WebEngine.java

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
Location : startServer
Killed by : none
negated conditional → TIMED_OUT

66

1.1
Location : startServer
Killed by : none
removed call to com/renomad/minum/web/IServer::start → TIMED_OUT

69

1.1
Location : startServer
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced return value with null for com/renomad/minum/web/WebEngine::startServer → KILLED

80

1.1
Location : startSslServer
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

95

1.1
Location : startSslServer
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to com/renomad/minum/web/IServer::start → KILLED

98

1.1
Location : startSslServer
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced return value with null for com/renomad/minum/web/WebEngine::startSslServer → KILLED

106

1.1
Location : getKeyStoreResult
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

114

1.1
Location : getKeyStoreResult
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

120

1.1
Location : getKeyStoreResult
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

123

1.1
Location : getKeyStoreResult
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced return value with null for com/renomad/minum/web/WebEngine::getKeyStoreResult → KILLED

148

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

2.2
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

149

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

154

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

2.2
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

155

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
negated conditional → KILLED

159

1.1
Location : isProvidedKeystoreProperties
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
replaced Boolean return with True for com/renomad/minum/web/WebEngine::isProvidedKeystoreProperties → KILLED

2.2
Location : isProvidedKeystoreProperties
Killed by : none
negated conditional → TIMED_OUT

3.3
Location : isProvidedKeystoreProperties
Killed by : none
negated conditional → TIMED_OUT

170

1.1
Location : createSslSocketWithSpecificKeystore
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to java/security/KeyStore::load → KILLED

173

1.1
Location : createSslSocketWithSpecificKeystore
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to javax/net/ssl/KeyManagerFactory::init → KILLED

178

1.1
Location : createSslSocketWithSpecificKeystore
Killed by : com.renomad.minum.web.FullSystemTests.testFullSystem_EdgeCase_InstantlyClosed(com.renomad.minum.web.FullSystemTests)
removed call to javax/net/ssl/SSLContext::init → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0