From a27873bedd9bb719f55732047413f8060be15036 Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Thu, 5 Aug 2021 16:39:16 +0200 Subject: [PATCH] Added ACME client and possibility to use the cert with the HttpServer --- build.gradle | 11 +- src/zutil/net/acme/AcmeClient.java | 227 ++++++++++++++++++ src/zutil/net/acme/AcmeDataStore.java | 34 +++ src/zutil/net/acme/AcmeFileDataStore.java | 64 +++++ src/zutil/net/http/HttpServer.java | 66 +++-- .../net/http/page/HttpStaticContentPage.java | 45 ++++ src/zutil/net/mqtt/MqttBroker.java | 2 +- .../threaded/ThreadedTCPNetworkServer.java | 149 +++++++----- src/zutil/osal/app/ffmpeg/FFmpeg.java | 12 +- .../app/ffmpeg/FFmpegProgressManager.java | 4 +- .../net/http/page/HttpGuessTheNumber.java | 2 +- 11 files changed, 527 insertions(+), 89 deletions(-) create mode 100644 src/zutil/net/acme/AcmeClient.java create mode 100644 src/zutil/net/acme/AcmeDataStore.java create mode 100644 src/zutil/net/acme/AcmeFileDataStore.java create mode 100644 src/zutil/net/http/page/HttpStaticContentPage.java diff --git a/build.gradle b/build.gradle index 09927b8..305add0 100644 --- a/build.gradle +++ b/build.gradle @@ -9,14 +9,19 @@ repositories { } dependencies { - implementation 'commons-fileupload:commons-fileupload:1.4' - implementation 'commons-io:commons-io:2.7' implementation 'org.dom4j:dom4j:2.1.3' - implementation 'org.xerial:sqlite-jdbc:3.8.11.2' compileOnly 'mysql:mysql-connector-java:8.0.16' + compileOnly 'org.xerial:sqlite-jdbc:3.8.11.2' + compileOnly 'javax.servlet:javax.servlet-api:3.1.0' + compileOnly 'org.shredzone.acme4j:acme4j-client:2.12' + compileOnly 'org.shredzone.acme4j:acme4j-utils:2.12' + + compileOnly 'commons-fileupload:commons-fileupload:1.4' + compileOnly 'commons-io:commons-io:2.7' + testImplementation 'junit:junit:4.13.1' testImplementation 'org.hamcrest:hamcrest-core:1.3' testImplementation 'com.carrotsearch:junit-benchmarks:0.7.2' diff --git a/src/zutil/net/acme/AcmeClient.java b/src/zutil/net/acme/AcmeClient.java new file mode 100644 index 0000000..6c53463 --- /dev/null +++ b/src/zutil/net/acme/AcmeClient.java @@ -0,0 +1,227 @@ +package zutil.net.acme; + +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.security.KeyPair; +import java.security.Security; +import java.security.cert.X509Certificate; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.shredzone.acme4j.Account; +import org.shredzone.acme4j.AccountBuilder; +import org.shredzone.acme4j.Authorization; +import org.shredzone.acme4j.Certificate; +import org.shredzone.acme4j.Order; +import org.shredzone.acme4j.Session; +import org.shredzone.acme4j.Status; +import org.shredzone.acme4j.challenge.Http01Challenge; +import org.shredzone.acme4j.exception.AcmeException; +import org.shredzone.acme4j.util.CSRBuilder; +import org.shredzone.acme4j.util.KeyPairUtils; +import zutil.StringUtil; +import zutil.log.LogUtil; +import zutil.net.http.HttpServer; +import zutil.net.http.page.HttpStaticContentPage; + +/** + * A class implementing the ACME protocol (Automatic Certificate Management Environment) used by the LetsEncrypt service. + * + * Code based on the example from the acme4j project: + * https://github.com/shred/acme4j/blob/master/acme4j-example/src/main/java/org/shredzone/acme4j/example/ClientTest.java + */ +public class AcmeClient { + private static final Logger logger = LogUtil.getLogger(); + + public static final String ACME_SERVER_LETSENCRYPT_PRODUCTION = "acme://letsencrypt.org"; + public static final String ACME_SERVER_LETSENCRYPT_STAGING = "acme://letsencrypt.org/staging"; + + private static final int KEY_SIZE = 2048; // RSA key size of generated key pairs + + + private String acmeServerUrl; + private AcmeDataStore dataStore; + + /** + * Create a new instance of the ACME Client + */ + public AcmeClient(AcmeDataStore dataStore, String acmeServerUrl) { + Security.addProvider(new BouncyCastleProvider()); + + this.dataStore = dataStore; + this.acmeServerUrl = acmeServerUrl; + } + + public AcmeClient(AcmeDataStore dataStore) { + this(dataStore, ACME_SERVER_LETSENCRYPT_PRODUCTION); + } + + + /** + * Generates a certificate for the given domains. Also takes care for the registration + * process. + * + * @param httpServer the web server where the challenge and response will be performed on and where the certificate will be applied to. + * @param domains the domains to get a certificates for + * @return a certificate for the given domains. + */ + public X509Certificate fetchCertificate(HttpServer httpServer, String... domains) throws IOException, AcmeException { + // ------------------------------------------------ + // Read in keys + // ------------------------------------------------ + + KeyPair userKeyPair = dataStore.loadUserKeyPair(); // Load the user key file. If there is no key file, create a new one. + KeyPair domainKeyPair = dataStore.loadDomainKeyPair(); // Load or create a key pair for the domains. This should not be the userKeyPair! + + if (userKeyPair == null) { + userKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE); + dataStore.storeUserKeyPair(userKeyPair); + } + if (domainKeyPair == null) { + domainKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE); + dataStore.storeDomainKeyPair(domainKeyPair); + } + + // ------------------------------------------------ + // Start authorization process + // ------------------------------------------------ + + Session session = new Session(acmeServerUrl); + Account acct = getAccount(session, userKeyPair); // Get the Account. If there is no account yet, create a new one. + Order order = acct.newOrder().domains(domains).create(); // Order the certificate + + // Perform all required authorizations + for (Authorization auth : order.getAuthorizations()) { + execHttpChallenge(auth, httpServer); + } + + // Generate a "Certificate Signing Request" for all of the domains, and sign it with the domain key pair. + CSRBuilder csrBuilder = new CSRBuilder(); + csrBuilder.addDomains(domains); + csrBuilder.sign(domainKeyPair); + + order.execute(csrBuilder.getEncoded()); // Order the certificate + + // Wait for the order to complete + try { + for (int attempts = 0; attempts < 10; attempts--) { + // Did the order pass or fail? + if (order.getStatus() == Status.VALID) { + break; + } else if (order.getStatus() == Status.INVALID) { + throw new AcmeException("Certificate order has failed, reason: " + order.getError()); + } + + // Wait for a few seconds + Thread.sleep(100L + 500L * attempts); + + // Then update the status + order.update(); + } + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "Interrupted", ex); + } + + // Get the certificate + Certificate certificate = order.getCertificate(); + + logger.info("The certificate for domains '" + StringUtil.join(",", domains) + "' has been successfully generated."); + + return certificate.getCertificate(); + } + + + /** + * Finds your {@link Account} at the ACME server. It will be found by your user's + * public key. If your key is not known to the server yet, a new account will be + * created. + *

+ * This is a simple way of finding your {@link Account}. A better way is to get the + * URL of your new account with {@link Account#getLocation()} and store it somewhere. + * If you need to get access to your account later, reconnect to it via {@link + * Session#login(URL, KeyPair)} by using the stored location. + * + * @param session {@link Session} to bind with + * @return {@link Account} + */ + private Account getAccount(Session session, KeyPair accountKey) throws AcmeException { + // Ask the user to accept the TOS, if server provides us with a link. + URI tos = session.getMetadata().getTermsOfService(); + if (tos != null) { + logger.info("By using this service you accept the Terms of Service: " + tos); + } + + Account account = new AccountBuilder() + .agreeToTermsOfService() + .useKeyPair(accountKey) + .create(session); + logger.info("Registered a new user, URL: " + account.getLocation()); + + return account; + } + + /** + * Authorize a domain. It will be associated with your account, so you will be able to + * retrieve a signed certificate for the domain later. + * + * @param auth {@link Authorization} to perform + */ + private void execHttpChallenge(Authorization auth, HttpServer httpServer) throws AcmeException { + logger.info("Authorization for domain: " + auth.getIdentifier().getDomain()); + + // The authorization is already valid. No need to process a challenge. + if (auth.getStatus() == Status.VALID) { + return; + } + + // Find the desired challenge and prepare it. + Http01Challenge challenge = auth.findChallenge(Http01Challenge.class); + if (challenge == null) { + throw new AcmeException("Found no " + Http01Challenge.TYPE + " challenge."); + } + + // If the challenge is already verified, there's no need to execute it again. + if (challenge.getStatus() == Status.VALID) + return; + + String url = "http://" + auth.getIdentifier().getDomain(); + String path = "/.well-known/acme-challenge/" + challenge.getToken(); + String content = challenge.getAuthorization(); + + // Output the challenge, wait for acknowledge... + logger.fine("Adding challenge HttpPage at: " + url + path); + httpServer.setPage(path, new HttpStaticContentPage(content)); + + // Now trigger the challenge. + challenge.trigger(); + + // Poll for the challenge to complete. + try { + for (int attempts = 0; attempts < 10; attempts--) { + // Did the authorization fail? + if (challenge.getStatus() == Status.VALID) { + break; + } else if (challenge.getStatus() == Status.INVALID) { + throw new AcmeException("Certificate challenge failed: " + challenge.getError()); + } + + // Wait for a few seconds + Thread.sleep(100L + 500L * attempts); + + // Then update the status + challenge.update(); + } + + // All reattempts are used up and there is still no valid authorization? + if (challenge.getStatus() != Status.VALID) + throw new AcmeException("Failed to pass the challenge for domain " + auth.getIdentifier().getDomain()); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "Interrupted", ex); + } finally { + // Cleanup + httpServer.removePage(path); + } + } +} diff --git a/src/zutil/net/acme/AcmeDataStore.java b/src/zutil/net/acme/AcmeDataStore.java new file mode 100644 index 0000000..33f138c --- /dev/null +++ b/src/zutil/net/acme/AcmeDataStore.java @@ -0,0 +1,34 @@ +package zutil.net.acme; + +import java.security.KeyPair; + +public interface AcmeDataStore { + + /** + * Loads a user key pair. + * + * @return a KeyPair for the user account, null if no KeyPar was found. + */ + KeyPair loadUserKeyPair(); + + /** + * Stores a user key pair for later usage. + * + * @param keyPair the keys for the user account + */ + void storeUserKeyPair(KeyPair keyPair); + + /** + * Loads a domain key pair. + * + * @return a KeyPair for the domains, null if no KeyPar was found. + */ + KeyPair loadDomainKeyPair(); + + /** + * Stores a domain key pair for later usage. + * + * @param keyPair the keys for the domain + */ + void storeDomainKeyPair(KeyPair keyPair); + } \ No newline at end of file diff --git a/src/zutil/net/acme/AcmeFileDataStore.java b/src/zutil/net/acme/AcmeFileDataStore.java new file mode 100644 index 0000000..2805580 --- /dev/null +++ b/src/zutil/net/acme/AcmeFileDataStore.java @@ -0,0 +1,64 @@ +package zutil.net.acme; + +import org.shredzone.acme4j.util.KeyPairUtils; + +import java.io.*; +import java.security.KeyPair; + +public class AcmeFileDataStore implements AcmeDataStore { + + private final File userKeyFile; + private final File domainKeyFile; + private final File domainCsrFile; + private final File domainChainFile; + + + /** + * Create a new file based datastore for storing ACME protocol needed data. + * + * @param folder is the folder there the different files should be stored in. + */ + public AcmeFileDataStore(File folder) { + this.userKeyFile = new File(folder, "user.key"); + this.domainKeyFile = new File(folder, "domain.key"); + this.domainCsrFile = new File(folder, "domain.csr"); + this.domainChainFile = new File(folder, "domain-chain.crt"); + } + + + public KeyPair loadUserKeyPair(){ + if (domainKeyFile.exists()) { + try (FileReader fr = new FileReader(domainKeyFile)) { + return KeyPairUtils.readKeyPair(fr); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + public void storeUserKeyPair(KeyPair keyPair){ + try (FileWriter fw = new FileWriter(userKeyFile)) { + KeyPairUtils.writeKeyPair(keyPair, fw); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public KeyPair loadDomainKeyPair() { + if (userKeyFile.exists()) { + try (FileReader fr = new FileReader(userKeyFile)) { + return KeyPairUtils.readKeyPair(fr); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + public void storeDomainKeyPair(KeyPair keyPair){ + try (FileWriter fw = new FileWriter(userKeyFile)) { + KeyPairUtils.writeKeyPair(keyPair, fw); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/zutil/net/http/HttpServer.java b/src/zutil/net/http/HttpServer.java index e97aa5e..7d104f5 100755 --- a/src/zutil/net/http/HttpServer.java +++ b/src/zutil/net/http/HttpServer.java @@ -61,21 +61,22 @@ public class HttpServer extends ThreadedTCPNetworkServer{ public static final int SESSION_TTL = 10*60*1000; // in milliseconds - private Map pages; - private HttpPage defaultPage; - private Map> sessions; - private int nextSessionId; + private Map pages = new ConcurrentHashMap<>();; + private Map> sessions = new ConcurrentHashMap<>();; + private int nextSessionId = 0; + private HttpPage defaultPage = null; /** * Creates a new instance of the sever * * @param port The port that the server should listen to */ - public HttpServer(int port) { - this(port, null, null); + public HttpServer(int port) throws IOException { + super(port); + initGarbageCollector(); + + logger.info("HTTP Server ready and listening to port: " + port); } - - /** * Creates a new instance of the sever * @@ -83,19 +84,17 @@ public class HttpServer extends ThreadedTCPNetworkServer{ * @param keyStore If this is not null then the server will use SSL connection with this keyStore file path * @param keyStorePass If this is not null then the server will use a SSL connection with the given certificate */ - public HttpServer(int port, File keyStore, String keyStorePass) { + public HttpServer(int port, File keyStore, char[] keyStorePass) throws IOException { super(port, keyStore, keyStorePass); + initGarbageCollector(); - pages = new ConcurrentHashMap<>(); - sessions = new ConcurrentHashMap<>(); - nextSessionId = 0; - - ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); - exec.scheduleWithFixedDelay(new SessionGarbageCollector(), 10000, SESSION_TTL / 2, TimeUnit.MILLISECONDS); - - logger.info("HTTP" + (keyStore==null ? "" : "S") + " Server ready and listening to port: " + port); + logger.info("HTTPS Server ready and listening to port: " + port); } + private void initGarbageCollector() { + ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); + exec.scheduleWithFixedDelay(new SessionGarbageCollector(), 10000, SESSION_TTL / 2, TimeUnit.MILLISECONDS); + } /** * This class acts as an garbage collector that * removes old sessions from the session HashMap @@ -118,16 +117,37 @@ public class HttpServer extends ThreadedTCPNetworkServer{ } } + /** - * Add a HttpPage to a specific URL + * Add a HttpPage to a specific URL. * - * @param name The URL or name of the page + * @param url The URL or name of the page * @param page The page itself */ - public void setPage(String name, HttpPage page) { - if (name.charAt(0) != '/') - name = "/" +name; - pages.put(name, page); + public void setPage(String url, HttpPage page) { + if (url.charAt(0) != '/') + url = "/" + url; + pages.put(url, page); + } + + /** + * Add all pages to this server from the given server object. + * + * @param server is the HttpServer object that pages will be copied from. + */ + public void setPages(HttpServer server) { + pages.putAll(server.pages); + } + + /** + * Removes a page based on the URL. + * + * @param url The URL or name of the page + */ + public void removePage(String url) { + if (url.charAt(0) != '/') + url = "/" + url; + pages.remove(url); } /** diff --git a/src/zutil/net/http/page/HttpStaticContentPage.java b/src/zutil/net/http/page/HttpStaticContentPage.java new file mode 100644 index 0000000..99fcb1b --- /dev/null +++ b/src/zutil/net/http/page/HttpStaticContentPage.java @@ -0,0 +1,45 @@ +package zutil.net.http.page; + +import zutil.net.http.HttpHeader; +import zutil.net.http.HttpPage; +import zutil.net.http.HttpPrintStream; + +import java.io.IOException; +import java.util.Map; + + +/** + * A page serving static content + */ +public class HttpStaticContentPage implements HttpPage { + private String contentType; + private String content; + + + public HttpStaticContentPage(String content) { + this.setContent(content); + } + + + public void setContent(String content) { + this.content = content; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + + @Override + public void respond(HttpPrintStream out, + HttpHeader headers, + Map session, + Map cookie, + Map request) throws IOException { + if (contentType != null) + out.setHeader(HttpHeader.HEADER_CONTENT_TYPE, contentType); + + out.setHeader(HttpHeader.HEADER_CONTENT_LENGTH, String.valueOf(content.length())); + out.print(content); + } +} diff --git a/src/zutil/net/mqtt/MqttBroker.java b/src/zutil/net/mqtt/MqttBroker.java index b964279..b5cb2dc 100755 --- a/src/zutil/net/mqtt/MqttBroker.java +++ b/src/zutil/net/mqtt/MqttBroker.java @@ -55,7 +55,7 @@ public class MqttBroker extends ThreadedTCPNetworkServer { private MqttSubscriptionListener globalListener; private Map> subscriptionListeners = new HashMap<>(); - public MqttBroker() { + public MqttBroker() throws IOException { super(MQTT_PORT); } diff --git a/src/zutil/net/threaded/ThreadedTCPNetworkServer.java b/src/zutil/net/threaded/ThreadedTCPNetworkServer.java index 596020e..4037146 100755 --- a/src/zutil/net/threaded/ThreadedTCPNetworkServer.java +++ b/src/zutil/net/threaded/ThreadedTCPNetworkServer.java @@ -26,15 +26,17 @@ package zutil.net.threaded; import zutil.log.LogUtil; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocketFactory; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.cert.CertificateException; +import java.security.*; +import java.security.cert.X509Certificate; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.logging.Level; @@ -46,48 +48,106 @@ import java.util.logging.Logger; * * @author Ziver */ -public abstract class ThreadedTCPNetworkServer extends Thread{ +public abstract class ThreadedTCPNetworkServer extends Thread { private static final Logger logger = LogUtil.getLogger(); + private Executor executor = Executors.newCachedThreadPool(); private final int port; - private Executor executor; - private File keyStore; - private String keyStorePass; + private ServerSocket serverSocket; /** - * Creates a new instance of the sever + * Creates a new instance of the sever. * - * @param port The port that the server should listen to + * @param port the port that the server should listen to */ - public ThreadedTCPNetworkServer(int port) { - this(port, null, null); + public ThreadedTCPNetworkServer(int port) throws IOException { + this.port = port; + this.serverSocket = new ServerSocket(port); } /** - * Creates a new instance of the sever + * Creates a new SSL instance of the sever. * - * @param port The port that the server should listen to - * @param keyStore If this is not null then the server will use SSL connection with this keyStore file path - * @param keyStorePass If this is not null then the server will use a SSL connection with the given certificate + * @param port the port that the server should listen to. + * @param keyStoreFile the path to the key store file containing the server certificates + * @param keyStorePass the password to decrypt the key store file. */ - public ThreadedTCPNetworkServer(int port, File keyStore, String keyStorePass) { + public ThreadedTCPNetworkServer(int port, File keyStoreFile, char[] keyStorePass) throws IOException { this.port = port; - executor = Executors.newCachedThreadPool(); - this.keyStorePass = keyStorePass; - this.keyStore = keyStore; + this.serverSocket = createSSLSocket(port, keyStoreFile, keyStorePass); + } + /** + * Creates a new SSL instance of the sever. + * + * @param port the port that the server should listen to. + * @param certificate the certificate for the servers domain. + */ + public ThreadedTCPNetworkServer(int port, X509Certificate certificate) throws IOException { + this.port = port; + this.serverSocket = createSSLSocket(port, certificate); + } + + /** + * Initiates a SSLServerSocket + * + * @param port the port the server should to + * @param keyStoreFile the cert file location + * @param keyStorePass the password for the cert file + * @return a SSLServerSocket object + */ + private static ServerSocket createSSLSocket(int port, File keyStoreFile, char[] keyStorePass) throws IOException{ + try { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(new FileInputStream(keyStoreFile), keyStorePass); + + return createSSLSocket(port, keyStore, keyStorePass); + } catch (GeneralSecurityException e) { + throw new IOException("Unable to configure certificate.", e); + } + } + + /** + * Initiates a SSLServerSocket + * + * @param port the port the server should to. + * @param certificate the certificate for the servers domain. + * @return a SSLServerSocket object + */ + private static ServerSocket createSSLSocket(int port, X509Certificate certificate) throws IOException{ + try { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(null); // Create empty keystore + keyStore.setCertificateEntry("main_certificate", certificate); + + return createSSLSocket(port, keyStore, null); + } catch (GeneralSecurityException e) { + throw new IOException("Unable to configure certificate.", e); + } + } + + /** + * Initiates a SSLServerSocket + * + * @param port the port the server should to. + * @param keyStore the key store containing the domain certificates + * @param keyStorePass the password for the cert file + * @return a SSLServerSocket object + */ + private static ServerSocket createSSLSocket(int port, KeyStore keyStore, char[] keyStorePass) + throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, KeyManagementException { + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, keyStorePass); + + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(keyManagerFactory.getKeyManagers(), null, SecureRandom.getInstanceStrong()); + SSLServerSocketFactory socketFactory = ctx.getServerSocketFactory(); + + return socketFactory.createServerSocket(port); } public void run() { - ServerSocket serverSocket = null; try { - if (keyStorePass != null && keyStore != null) { - registerCertificate(keyStore, keyStorePass); - serverSocket = initSSL(port); - } - else { - serverSocket = new ServerSocket(port); - } - logger.info("Listening for TCP Connections on port: " +port); + logger.info("Listening for TCP Connections on port: " + port); while (true) { Socket connectionSocket = serverSocket.accept(); @@ -105,7 +165,9 @@ public abstract class ThreadedTCPNetworkServer extends Thread{ } finally { if (serverSocket != null) { try { + logger.info("Closing TCP socket listener (Port: " + port + ")."); serverSocket.close(); + serverSocket = null; } catch(IOException e) { logger.log(Level.SEVERE, null, e); } } } @@ -116,33 +178,10 @@ public abstract class ThreadedTCPNetworkServer extends Thread{ * that will handle the newly made connection, if an null value is returned * then the ThreadedTCPNetworkServer will close the new connection. * - * @param s is an new connection to an host - * @return a new instance of an thread or null + * @param socket is an new connection to an host + * @return a new instance of an thread or null */ - protected abstract ThreadedTCPNetworkServerThread getThreadInstance(Socket s) throws IOException; - - /** - * Initiates a SSLServerSocket - * - * @param port The port to listen to - * @return The SSLServerSocket - */ - private ServerSocket initSSL(int port) throws IOException{ - SSLServerSocketFactory sslserversocketfactory = - (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); - return sslserversocketfactory.createServerSocket(port); - - } - - /** - * Registers the given cert file to the KeyStore - * - * @param keyStore The cert file - */ - protected void registerCertificate(File keyStore, String keyStorePass) { - System.setProperty("javax.net.ssl.keyStore", keyStore.getAbsolutePath()); - System.setProperty("javax.net.ssl.keyStorePassword", keyStorePass); - } + protected abstract ThreadedTCPNetworkServerThread getThreadInstance(Socket socket) throws IOException; /** * Stops the server and interrupts its internal thread. diff --git a/src/zutil/osal/app/ffmpeg/FFmpeg.java b/src/zutil/osal/app/ffmpeg/FFmpeg.java index 979878e..fceff9f 100644 --- a/src/zutil/osal/app/ffmpeg/FFmpeg.java +++ b/src/zutil/osal/app/ffmpeg/FFmpeg.java @@ -27,6 +27,7 @@ package zutil.osal.app.ffmpeg; import zutil.osal.app.ffmpeg.FFmpegConstants.*; import zutil.osal.app.ffmpeg.FFmpegProgressManager.FFmpegProgressListener; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -36,7 +37,6 @@ import java.util.List; * @see FFmpeg Commandline Documentation */ public class FFmpeg { - private FFmpegLogLevel logLevel; private boolean overwriteOutput = false; private List inputs = new ArrayList<>(); @@ -71,9 +71,13 @@ public class FFmpeg { if (listener == null) throw new IllegalArgumentException("FFmpegProgressListener cannot be NULL."); - if (progressManager != null) - progressManager.close(); - progressManager = new FFmpegProgressManager(listener); + try { + if (progressManager != null) + progressManager.close(); + progressManager = new FFmpegProgressManager(listener); + } catch (IOException e) { + throw new RuntimeException(e); + } } diff --git a/src/zutil/osal/app/ffmpeg/FFmpegProgressManager.java b/src/zutil/osal/app/ffmpeg/FFmpegProgressManager.java index ba0dd96..7c25ad4 100644 --- a/src/zutil/osal/app/ffmpeg/FFmpegProgressManager.java +++ b/src/zutil/osal/app/ffmpeg/FFmpegProgressManager.java @@ -91,10 +91,10 @@ public class FFmpegProgressManager extends ThreadedTCPNetworkServer { private FFmpegProgressListener listener; private String address; - public FFmpegProgressManager(FFmpegProgressListener listener) { + public FFmpegProgressManager(FFmpegProgressListener listener) throws IOException { this(listener, PROGRESS_DEFAULT_PORT); } - public FFmpegProgressManager(FFmpegProgressListener listener, int port) { + public FFmpegProgressManager(FFmpegProgressListener listener, int port) throws IOException { super(port); this.listener = listener; this.address = "tcp://" + InetAddress.getLoopbackAddress() + ":" + port; diff --git a/test/zutil/net/http/page/HttpGuessTheNumber.java b/test/zutil/net/http/page/HttpGuessTheNumber.java index a3e05b4..4728ef9 100755 --- a/test/zutil/net/http/page/HttpGuessTheNumber.java +++ b/test/zutil/net/http/page/HttpGuessTheNumber.java @@ -46,7 +46,7 @@ public class HttpGuessTheNumber implements HttpPage { private static final String COOKIE_KEY_HIGH = "high"; - public static void main(String[] args) { + public static void main(String[] args) throws IOException { LogUtil.setGlobalLevel(Level.ALL); LogUtil.setGlobalFormatter(new CompactLogFormatter());