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