| 1 | package com.renomad.minum.web; | |
| 2 | ||
| 3 | import com.renomad.minum.state.Constants; | |
| 4 | import com.renomad.minum.logging.ILogger; | |
| 5 | import com.renomad.minum.state.Context; | |
| 6 | ||
| 7 | import javax.net.ssl.KeyManagerFactory; | |
| 8 | import javax.net.ssl.SSLContext; | |
| 9 | import java.io.InputStream; | |
| 10 | import java.net.ServerSocket; | |
| 11 | import java.net.URL; | |
| 12 | import java.nio.file.Path; | |
| 13 | import java.security.*; | |
| 14 | import java.util.concurrent.ExecutorService; | |
| 15 | ||
| 16 | import static com.renomad.minum.web.HttpServerType.ENCRYPTED_HTTP; | |
| 17 | import static com.renomad.minum.web.HttpServerType.PLAIN_TEXT_HTTP; | |
| 18 | ||
| 19 | /** | |
| 20 | * This class contains the basic internet capabilities. | |
| 21 | * <br><br> | |
| 22 | * Think of this class as managing some of the lowest-level internet | |
| 23 | * communications we need to handle to support a web application. Sockets, | |
| 24 | * Servers, Threads, that kind of stuff. | |
| 25 | */ | |
| 26 | final class WebEngine { | |
| 27 | ||
| 28 | private final Constants constants; | |
| 29 | private final Context context; | |
| 30 | private final ExecutorService executorService; | |
| 31 | private final WebFramework webFramework; | |
| 32 | ||
| 33 | WebEngine(Context context, WebFramework webFramework) { | |
| 34 | this.logger = context.getLogger(); | |
| 35 | this.logger.logDebug(() -> "Using a supplied logger in WebEngine"); | |
| 36 | this.constants = context.getConstants(); | |
| 37 | this.context = context; | |
| 38 | this.executorService = context.getExecutorService(); | |
| 39 | this.webFramework = webFramework; | |
| 40 | } | |
| 41 | ||
| 42 | private final ILogger logger; | |
| 43 | static final String HTTP_CRLF = "\r\n"; | |
| 44 | ||
| 45 | /** | |
| 46 | * Start a plain-text unencrypted server, listening on the port specified by the user | |
| 47 | */ | |
| 48 | IServer startServer() { | |
| 49 | int port = constants.serverPort; | |
| 50 | ||
| 51 | // if the user sets a value of -1 for the port, it indicates that they want | |
| 52 | // to disable that server. Reason for this are if they want just one server running. | |
| 53 |
1
1. startServer : negated conditional → TIMED_OUT |
if (port == -1) { |
| 54 | return null; | |
| 55 | } | |
| 56 | | |
| 57 | ServerSocket ss; | |
| 58 | try { | |
| 59 | ss = new ServerSocket(port); | |
| 60 | } catch (Exception e) { | |
| 61 | throw new WebServerException("Failed to create serversocket on port " + port, e); | |
| 62 | } | |
| 63 | logger.logDebug(() -> String.format("Just created a new ServerSocket: %s", ss)); | |
| 64 | IServer server = new Server(ss, context, "http server", webFramework, executorService, PLAIN_TEXT_HTTP); | |
| 65 | logger.logDebug(() -> String.format("Just created a new Server: %s", server)); | |
| 66 |
1
1. startServer : removed call to com/renomad/minum/web/IServer::start → TIMED_OUT |
server.start(); |
| 67 | String hostname = constants.hostName; | |
| 68 | logger.logDebug(() -> String.format("%s started at http://%s:%s", server, hostname, server.getPort())); | |
| 69 |
1
1. startServer : replaced return value with null for com/renomad/minum/web/WebEngine::startServer → KILLED |
return server; |
| 70 | } | |
| 71 | ||
| 72 | /** | |
| 73 | * Start an encrypted server, using TLS 1.3 | |
| 74 | */ | |
| 75 | IServer startSslServer() { | |
| 76 | int port = constants.secureServerPort; | |
| 77 | ||
| 78 | // if the user sets a value of -1 for the port, it indicates that they want | |
| 79 | // to disable that server. Reason for this are if they want just one server running. | |
| 80 |
1
1. startSslServer : negated conditional → KILLED |
if (port == -1) { |
| 81 | return null; | |
| 82 | } | |
| 83 | ||
| 84 | /* | |
| 85 | * If we are provided details for a keystore (its location and password), use it | |
| 86 | */ | |
| 87 | final var useExternalKeystore = isProvidedKeystoreProperties(constants.keystorePath, constants.keystorePassword, logger); | |
| 88 | KeyStoreResult keystoreResult = getKeyStoreResult(useExternalKeystore, constants.keystorePath, constants.keystorePassword, logger); | |
| 89 | ||
| 90 | ||
| 91 | ServerSocket ss = createSslSocketWithSpecificKeystore(port, keystoreResult.keystoreUrl(), keystoreResult.keystorePassword()); | |
| 92 | logger.logDebug(() -> String.format("Just created a new ServerSocket: %s", ss)); | |
| 93 | IServer server = new Server(ss, context, "https server", webFramework, executorService, ENCRYPTED_HTTP); | |
| 94 | logger.logDebug(() -> String.format("Just created a new SSL Server: %s", server)); | |
| 95 |
1
1. startSslServer : removed call to com/renomad/minum/web/IServer::start → KILLED |
server.start(); |
| 96 | String hostname = constants.hostName; | |
| 97 | logger.logDebug(() -> String.format("%s started at https://%s:%s", server, hostname, server.getPort())); | |
| 98 |
1
1. startSslServer : replaced return value with null for com/renomad/minum/web/WebEngine::startSslServer → KILLED |
return server; |
| 99 | } | |
| 100 | ||
| 101 | static KeyStoreResult getKeyStoreResult( | |
| 102 | boolean useExternalKeystore, | |
| 103 | String keystorePath, | |
| 104 | String keystorePassword, | |
| 105 | ILogger logger) { | |
| 106 |
1
1. getKeyStoreResult : negated conditional → KILLED |
if (useExternalKeystore) { |
| 107 | logger.logDebug(() -> "Using keystore and password referenced in minum.config"); | |
| 108 | } else { | |
| 109 | logger.logDebug(() -> "Using the default (self-signed / testing-only) certificate"); | |
| 110 | } | |
| 111 | ||
| 112 | final URL keystoreUrl; | |
| 113 | try { | |
| 114 |
1
1. getKeyStoreResult : negated conditional → KILLED |
keystoreUrl = useExternalKeystore ? |
| 115 | Path.of(keystorePath).toUri().toURL() : | |
| 116 | WebEngine.class.getResource("/certs/keystore"); | |
| 117 | } catch (Exception e) { | |
| 118 | throw new WebServerException("Error while building keystoreUrl: " + e); | |
| 119 | } | |
| 120 |
1
1. getKeyStoreResult : negated conditional → KILLED |
final String keystorePasswordFinal = useExternalKeystore ? |
| 121 | keystorePassword : | |
| 122 | "passphrase"; | |
| 123 |
1
1. getKeyStoreResult : replaced return value with null for com/renomad/minum/web/WebEngine::getKeyStoreResult → KILLED |
return new KeyStoreResult(keystoreUrl, keystorePasswordFinal); |
| 124 | } | |
| 125 | ||
| 126 | record KeyStoreResult(URL keystoreUrl, String keystorePassword) { } | |
| 127 | ||
| 128 | ||
| 129 | /** | |
| 130 | * Look into the system properties to see whether values have been | |
| 131 | * set for the keystore and keystorePassword keys. | |
| 132 | * <p> | |
| 133 | * the key for keystore is: javax.net.ssl.keyStore | |
| 134 | * the key for keystorePassword is: javax.net.ssl.keyStorePassword | |
| 135 | * <p> | |
| 136 | * It's smart, if you are creating a server that will run | |
| 137 | * with a genuine signed certificate, to have those files | |
| 138 | * stored somewhere and then set these system properties. That | |
| 139 | * way, it's a characteristic of a particular server - it's not | |
| 140 | * needed to bundle the certificate with the actual server in | |
| 141 | * any way. | |
| 142 | * <p> | |
| 143 | * We *do* bundle a cert, but it's for testing and is self-signed. | |
| 144 | */ | |
| 145 | static Boolean isProvidedKeystoreProperties(String keystorePath, String keystorePassword, ILogger logger) { | |
| 146 | ||
| 147 | // get the directory to the keystore from a system property | |
| 148 |
2
1. isProvidedKeystoreProperties : negated conditional → KILLED 2. isProvidedKeystoreProperties : negated conditional → KILLED |
boolean hasKeystore = ! (keystorePath == null || keystorePath.isBlank()); |
| 149 |
1
1. isProvidedKeystoreProperties : negated conditional → KILLED |
if (! hasKeystore) { |
| 150 | logger.logDebug(() -> "Keystore system property was not set"); | |
| 151 | } | |
| 152 | ||
| 153 | // get the password to that keystore from a system property | |
| 154 |
2
1. isProvidedKeystoreProperties : negated conditional → KILLED 2. isProvidedKeystoreProperties : negated conditional → KILLED |
boolean hasKeystorePassword = ! (keystorePassword == null || keystorePassword.isBlank()); |
| 155 |
1
1. isProvidedKeystoreProperties : negated conditional → KILLED |
if (! hasKeystorePassword) { |
| 156 | logger.logDebug(() -> "keystorePassword system property was not set"); | |
| 157 | } | |
| 158 | ||
| 159 |
3
1. isProvidedKeystoreProperties : negated conditional → TIMED_OUT 2. isProvidedKeystoreProperties : negated conditional → TIMED_OUT 3. isProvidedKeystoreProperties : replaced Boolean return with True for com/renomad/minum/web/WebEngine::isProvidedKeystoreProperties → KILLED |
return hasKeystore && hasKeystorePassword; |
| 160 | } | |
| 161 | ||
| 162 | ||
| 163 | /** | |
| 164 | * Create an SSL Socket using a specified keystore | |
| 165 | */ | |
| 166 | ServerSocket createSslSocketWithSpecificKeystore(int sslPort, URL keystoreUrl, String keystorePassword) { | |
| 167 | try (InputStream keystoreInputStream = keystoreUrl.openStream()) { | |
| 168 | final var keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); | |
| 169 | char[] passwordCharArray = keystorePassword.toCharArray(); | |
| 170 |
1
1. createSslSocketWithSpecificKeystore : removed call to java/security/KeyStore::load → KILLED |
keyStore.load(keystoreInputStream, passwordCharArray); |
| 171 | ||
| 172 | final var keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); | |
| 173 |
1
1. createSslSocketWithSpecificKeystore : removed call to javax/net/ssl/KeyManagerFactory::init → KILLED |
keyManagerFactory.init(keyStore, passwordCharArray); |
| 174 | ||
| 175 | final var keyManagers = keyManagerFactory.getKeyManagers(); | |
| 176 | ||
| 177 | final var sslContext = SSLContext.getInstance("TLSv1.3"); | |
| 178 |
1
1. createSslSocketWithSpecificKeystore : removed call to javax/net/ssl/SSLContext::init → KILLED |
sslContext.init(keyManagers, null, new SecureRandom()); |
| 179 | ||
| 180 | final var socketFactory = sslContext.getServerSocketFactory(); | |
| 181 | return socketFactory.createServerSocket(sslPort); | |
| 182 | } catch (Exception ex) { | |
| 183 | String extraMessage = "Exception during creation of SSL socket with port %d, keystore URL of %s. Exception message: %s".formatted(sslPort, keystoreUrl, ex.getMessage()); | |
| 184 | logger.logDebug(() -> extraMessage); | |
| 185 | throw new WebServerException(extraMessage, ex); | |
| 186 | } | |
| 187 | } | |
| 188 | ||
| 189 | } | |
Mutations | ||
| 53 |
1.1 |
|
| 66 |
1.1 |
|
| 69 |
1.1 |
|
| 80 |
1.1 |
|
| 95 |
1.1 |
|
| 98 |
1.1 |
|
| 106 |
1.1 |
|
| 114 |
1.1 |
|
| 120 |
1.1 |
|
| 123 |
1.1 |
|
| 148 |
1.1 2.2 |
|
| 149 |
1.1 |
|
| 154 |
1.1 2.2 |
|
| 155 |
1.1 |
|
| 159 |
1.1 2.2 3.3 |
|
| 170 |
1.1 |
|
| 173 |
1.1 |
|
| 178 |
1.1 |