12
12
import org .apache .http .config .SocketConfig ;
13
13
import org .apache .http .impl .bootstrap .HttpServer ;
14
14
import org .apache .http .impl .bootstrap .ServerBootstrap ;
15
+ import org .apache .http .ssl .SSLContexts ;
15
16
16
17
import javax .annotation .Nonnull ;
17
18
import javax .annotation .Nullable ;
19
+ import javax .net .ssl .SSLContext ;
18
20
import java .io .File ;
19
21
import java .io .IOException ;
20
22
import java .net .BindException ;
23
25
import java .nio .file .NoSuchFileException ;
24
26
import java .nio .file .Path ;
25
27
import java .nio .file .Paths ;
28
+ import java .security .KeyManagementException ;
29
+ import java .security .KeyStoreException ;
30
+ import java .security .NoSuchAlgorithmException ;
31
+ import java .security .UnrecoverableKeyException ;
32
+ import java .security .cert .CertificateException ;
26
33
import java .text .MessageFormat ;
27
34
import java .util .Collection ;
28
35
import java .util .Collections ;
@@ -117,11 +124,21 @@ public XatkitServer(Configuration configuration) {
117
124
(), Configuration .class .getSimpleName (), configuration );
118
125
Log .info ("Creating {0}" , this .getClass ().getSimpleName ());
119
126
this .isStarted = false ;
127
+ if (configuration .containsKey (XatkitServerUtils .SERVER_KEYSTORE_LOCATION_KEY )
128
+ && !configuration .containsKey (XatkitServerUtils .SERVER_PUBLIC_URL_KEY )) {
129
+ /*
130
+ * We could automatically set the default URL to https://localhost, but it doesn't really make sense. If
131
+ * this is an issue we can update XatkitServer's behavior to adapt the default public URL when there is
132
+ * an SSL context.
133
+ */
134
+ throw new XatkitException (MessageFormat .format ("Cannot start the {0}: the configuration contains a " +
135
+ "keystore location but does not contain the server''s public_url ({1}))" ,
136
+ this .getClass ().getSimpleName (), XatkitServerUtils .SERVER_PUBLIC_URL_KEY ));
137
+ }
120
138
String publicUrl = configuration .getString (XatkitServerUtils .SERVER_PUBLIC_URL_KEY ,
121
139
XatkitServerUtils .DEFAULT_SERVER_LOCATION );
122
140
this .port = configuration .getInt (XatkitServerUtils .SERVER_PORT_KEY , XatkitServerUtils .DEFAULT_SERVER_PORT );
123
141
this .baseURL = publicUrl + ":" + Integer .toString (this .port );
124
- Log .info ("{0} started on {1}" , this .getClass ().getSimpleName (), this .getBaseURL ());
125
142
this .restEndpoints = new HashMap <>();
126
143
this .contentDirectory = FileUtils .getFile (XatkitServerUtils .PUBLIC_DIRECTORY_NAME , configuration );
127
144
this .contentDirectory .mkdirs ();
@@ -131,6 +148,8 @@ public XatkitServer(Configuration configuration) {
131
148
} catch (IOException e ) {
132
149
throw new XatkitException ("Cannot initialize the Xatkit server, see the attached exception" , e );
133
150
}
151
+ SSLContext sslContext = createSSLContext (configuration );
152
+
134
153
SocketConfig socketConfig = SocketConfig .custom ()
135
154
.setSoTimeout (15000 )
136
155
.setTcpNoDelay (true )
@@ -139,6 +158,10 @@ public XatkitServer(Configuration configuration) {
139
158
server = ServerBootstrap .bootstrap ()
140
159
.setListenerPort (port )
141
160
.setServerInfo ("Xatkit/1.1" )
161
+ /*
162
+ * createSSLContext is @Nullable: setting a null SSLContext is similar to not setting it.
163
+ */
164
+ .setSslContext (sslContext )
142
165
.setSocketConfig (socketConfig )
143
166
.setExceptionLogger (e -> {
144
167
if (e instanceof SocketTimeoutException ) {
@@ -157,6 +180,53 @@ public XatkitServer(Configuration configuration) {
157
180
.create ();
158
181
}
159
182
183
+ /**
184
+ * Creates a {@link SSLContext} from the provided {@code configuration}.
185
+ *
186
+ * @param configuration the {@link Configuration} containing the SSL configuration
187
+ * @return the {@link SSLContext}, or {@code null} if the provided {@code configuration} does not contain an SSL
188
+ * configuration
189
+ * @throws XatkitException if the provided keystore does not exist of if an error occurred when loading the
190
+ * keystore content
191
+ * @throws NullPointerException if the {@code configuration} contains a keystore location but does not contain a
192
+ * store/key password
193
+ */
194
+ private @ Nullable
195
+ SSLContext createSSLContext (@ Nonnull Configuration configuration ) {
196
+ checkNotNull (configuration , "Cannot get the %s from the provided %s %s" , SSLContext .class .getSimpleName (),
197
+ Configuration .class .getSimpleName (), configuration );
198
+ String keystorePath = configuration .getString (XatkitServerUtils .SERVER_KEYSTORE_LOCATION_KEY );
199
+ if (isNull (keystorePath )) {
200
+ Log .info ("No SSL context to load" );
201
+ return null ;
202
+ }
203
+ File keystoreFile = FileUtils .getFile (keystorePath , configuration );
204
+ if (keystoreFile .exists ()) {
205
+ String storePassword = configuration .getString (XatkitServerUtils .SERVER_KEYSTORE_STORE_PASSWORD_KEY );
206
+ String keyPassword = configuration .getString (XatkitServerUtils .SERVER_KEYSTORE_KEY_PASSWORD_KEY );
207
+ checkNotNull (storePassword , "Cannot load the provided keystore, property %s not set" ,
208
+ XatkitServerUtils .SERVER_KEYSTORE_STORE_PASSWORD_KEY );
209
+ checkNotNull (keyPassword , "Cannot load the provided keystore, property %s not set" ,
210
+ XatkitServerUtils .SERVER_KEYSTORE_KEY_PASSWORD_KEY );
211
+ SSLContext sslContext = null ;
212
+
213
+ try {
214
+ sslContext = SSLContexts .custom ().loadKeyMaterial (keystoreFile ,
215
+ storePassword .toCharArray (),
216
+ keyPassword .toCharArray ())
217
+ .build ();
218
+ return sslContext ;
219
+ } catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException | CertificateException | IOException | KeyManagementException e ) {
220
+ throw new XatkitException (MessageFormat .format ("Cannot get the {0}: an error occurred when loading " +
221
+ "the keystore, see attached exception" , SSLContext .class .getSimpleName ()));
222
+
223
+ }
224
+ } else {
225
+ throw new XatkitException (MessageFormat .format ("Cannot get the {0} from the provided keystore location " +
226
+ "{1}: the file does not exist" , SSLContext .class .getSimpleName (), keystorePath ));
227
+ }
228
+ }
229
+
160
230
/**
161
231
* Returns the port the server is listening to.
162
232
*
@@ -650,8 +720,9 @@ private static EndpointEntry of(HttpMethod httpMethod, String uri) {
650
720
651
721
/**
652
722
* Constructs an {@link EndpointEntry} with the provided {@code httpMethod} and {@code uri}.
723
+ *
653
724
* @param httpMethod the Http method of the entry
654
- * @param uri the URI of the entry
725
+ * @param uri the URI of the entry
655
726
*/
656
727
private EndpointEntry (HttpMethod httpMethod , String uri ) {
657
728
this .httpMethod = httpMethod ;
0 commit comments