@@ -129,6 +129,8 @@ public class SimpleHttpServer extends PluginService implements Authenticator {
129129 int websocketPort = DEFAULT_WEBSOCKET_PORT ;
130130 private String bindHostname = "localhost" ;
131131 private boolean httpsEnabled = DEFAULT_HTTPS_ENABLED ;
132+ private SslContext context ;
133+ private Provider <SSLEngine > engineProvider ;
132134
133135 @ Inject
134136 public SimpleHttpServer (Topics t , Kernel kernel , DeviceConfiguration deviceConfiguration ) {
@@ -183,68 +185,10 @@ public void postInject() {
183185 @ SuppressWarnings ("UseSpecificCatch" )
184186 @ Override
185187 public void startup () throws InterruptedException {
186- SslContext context = null ;
187- Provider < SSLEngine > engineProvider = null ;
188+ context = null ;
189+ engineProvider = null ;
188190 if (httpsEnabled ) {
189- Path workPath ;
190- KeyStore ks ;
191- try {
192- workPath = kernel .getNucleusPaths ().workPath (getServiceName ());
193- ks = KeyStore .getInstance ("JKS" );
194- } catch (IOException | KeyStoreException e ) {
195- serviceErrored (e );
196- return ;
197- }
198-
199- // Get passphrase or generate a new one to use for the keystore password
200- char [] passphrase = Coerce .toString (getRuntimeConfig ().lookup ("keystorePassphrase" )
201- .dflt (Utils .generateRandomString (24 ))).toCharArray ();
202-
203- // Either load cert/key from keystore or create and then save to keystore for later
204- Path keyStorePath = workPath .resolve ("keystore.jks" );
205- try {
206- if (Files .exists (keyStorePath )) {
207- try (InputStream is = Files .newInputStream (keyStorePath )) {
208- ks .load (is , passphrase );
209- }
210- } else {
211- // Initialize keystore as empty
212- ks .load (null , passphrase );
213-
214- // Generate keys and certificate
215- KeyPairGenerator keyGen = KeyPairGenerator .getInstance ("RSA" );
216- keyGen .initialize (4096 , new SecureRandom ());
217- KeyPair keyPair = keyGen .generateKeyPair ();
218- X509Certificate cert = selfSign (keyPair , bindHostname );
219-
220- ks .setCertificateEntry (CERT_NAME , cert );
221- ks .setKeyEntry (PRIVATE_KEY_NAME , keyPair .getPrivate (), new char [0 ], new Certificate []{cert });
222- try (OutputStream os = Files .newOutputStream (keyStorePath )) {
223- ks .store (os , passphrase );
224- }
225- }
226- } catch (IOException | NoSuchAlgorithmException | CertificateException
227- | KeyStoreException | OperatorCreationException e ) {
228- serviceErrored (e );
229- return ;
230- }
231-
232- try {
233- // Grab key and cert for SSL setup
234- PrivateKey privateKey = (PrivateKey ) ks .getKey (PRIVATE_KEY_NAME , new char [0 ]);
235- X509Certificate cert = (X509Certificate ) ks .getCertificate (CERT_NAME );
236- context = SslContextBuilder .forServer (privateKey , cert ).build ();
237- SslContext finalContext = context ;
238- engineProvider = () -> finalContext .newEngine (ByteBufAllocator .DEFAULT );
239-
240- // Save certificate fingerprint as space separated hex bytes
241- String fingerprint = fingerprintCert (cert , SHA_1_ALGORITHM );
242- config .getRoot ().lookup (CERT_FINGERPRINT_NAMESPACE , SHA_1_ALGORITHM ).withValue (fingerprint );
243- fingerprint = fingerprintCert (cert , SHA_256_ALGORITHM );
244- config .getRoot ().lookup (CERT_FINGERPRINT_NAMESPACE , SHA_256_ALGORITHM ).withValue (fingerprint );
245- } catch (NoSuchAlgorithmException | CertificateEncodingException
246- | KeyStoreException | UnrecoverableKeyException | SSLException e ) {
247- serviceErrored (e );
191+ if (!initializeHttps ()) {
248192 return ;
249193 }
250194 }
@@ -277,6 +221,86 @@ public void startup() throws InterruptedException {
277221 reportState (State .RUNNING );
278222 }
279223
224+ boolean initializeHttps () {
225+ Path workPath ;
226+ KeyStore ks ;
227+ try {
228+ workPath = kernel .getNucleusPaths ().workPath (getServiceName ());
229+ ks = KeyStore .getInstance ("JKS" );
230+ } catch (IOException | KeyStoreException e ) {
231+ serviceErrored (e );
232+ return true ;
233+ }
234+
235+ // Get passphrase or generate a new one to use for the keystore password
236+ char [] passphrase = Coerce .toString (getRuntimeConfig ().lookup ("keystorePassphrase" )
237+ .dflt (Utils .generateRandomString (24 ))).toCharArray ();
238+
239+ // Either load cert/key from keystore or create and then save to keystore for later
240+ Path keyStorePath = workPath .resolve ("keystore.jks" );
241+ try {
242+ if (Files .exists (keyStorePath )) {
243+ try (InputStream is = Files .newInputStream (keyStorePath )) {
244+ ks .load (is , passphrase );
245+ } catch (IOException e ) {
246+ // If the password is wrong for whatever reason, delete the existing keystore and
247+ // reinitialize it
248+ if (e .getCause () instanceof UnrecoverableKeyException ) {
249+ Files .deleteIfExists (keyStorePath );
250+ initializeKeyStore (ks , passphrase , keyStorePath );
251+ } else {
252+ throw e ;
253+ }
254+ }
255+ } else {
256+ initializeKeyStore (ks , passphrase , keyStorePath );
257+ }
258+ } catch (IOException | NoSuchAlgorithmException | CertificateException
259+ | KeyStoreException | OperatorCreationException e ) {
260+ serviceErrored (e );
261+ return false ;
262+ }
263+
264+ try {
265+ // Grab key and cert for SSL setup
266+ PrivateKey privateKey = (PrivateKey ) ks .getKey (PRIVATE_KEY_NAME , new char [0 ]);
267+ X509Certificate cert = (X509Certificate ) ks .getCertificate (CERT_NAME );
268+ context = SslContextBuilder .forServer (privateKey , cert ).build ();
269+ SslContext finalContext = context ;
270+ engineProvider = () -> finalContext .newEngine (ByteBufAllocator .DEFAULT );
271+
272+ // Save certificate fingerprint as space separated hex bytes
273+ String fingerprint = fingerprintCert (cert , SHA_1_ALGORITHM );
274+ config .getRoot ().lookup (CERT_FINGERPRINT_NAMESPACE , SHA_1_ALGORITHM ).withValue (fingerprint );
275+ fingerprint = fingerprintCert (cert , SHA_256_ALGORITHM );
276+ config .getRoot ().lookup (CERT_FINGERPRINT_NAMESPACE , SHA_256_ALGORITHM ).withValue (fingerprint );
277+ } catch (NoSuchAlgorithmException | CertificateEncodingException
278+ | KeyStoreException | UnrecoverableKeyException | SSLException e ) {
279+ serviceErrored (e );
280+ return false ;
281+ }
282+ return true ;
283+ }
284+
285+ private void initializeKeyStore (KeyStore ks , char [] passphrase , Path keyStorePath )
286+ throws IOException , NoSuchAlgorithmException , CertificateException , OperatorCreationException ,
287+ KeyStoreException {
288+ // Initialize keystore as empty
289+ ks .load (null , passphrase );
290+
291+ // Generate keys and certificate
292+ KeyPairGenerator keyGen = KeyPairGenerator .getInstance ("RSA" );
293+ keyGen .initialize (4096 , new SecureRandom ());
294+ KeyPair keyPair = keyGen .generateKeyPair ();
295+ X509Certificate cert = selfSign (keyPair , bindHostname );
296+
297+ ks .setCertificateEntry (CERT_NAME , cert );
298+ ks .setKeyEntry (PRIVATE_KEY_NAME , keyPair .getPrivate (), new char [0 ], new Certificate []{cert });
299+ try (OutputStream os = Files .newOutputStream (keyStorePath )) {
300+ ks .store (os , passphrase );
301+ }
302+ }
303+
280304 static String fingerprintCert (X509Certificate cert , String algorithm )
281305 throws NoSuchAlgorithmException , CertificateEncodingException {
282306 StringBuilder sb = new StringBuilder ();
0 commit comments