Skip to content

Commit f0b0a01

Browse files
MikeDomboshaguptashaikh
authored andcommitted
fix: reinitialize keystore if password is somehow incorrect (#16)
1 parent 8636b33 commit f0b0a01

File tree

2 files changed

+91
-61
lines changed

2 files changed

+91
-61
lines changed

src/main/java/com/aws/greengrass/localdebugconsole/SimpleHttpServer.java

+85-61
Original file line numberDiff line numberDiff line change
@@ -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();

src/test/java/com/aws/greengrass/localdebugconsole/SimpleHttpServerTest.java

+6
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ void GIVEN_server_WHEN_authenticate_THEN_cleans_storage() {
6060
abExpiration.withValue(0);
6161
assertFalse(http.isUsernameAndPasswordValid(new Pair<>("a", "b")));
6262
assertNull(kernel.getConfig().findTopics(DEBUG_PASSWORD_NAMESPACE, "a", "b"));
63+
64+
// Verify that if the password to the keystore is lost we can still initialize https properly
65+
assertTrue(http.initializeHttps());
66+
http.getRuntimeConfig().remove(); // remove runtime config so that the password is lost
67+
kernel.getContext().waitForPublishQueueToClear();
68+
assertTrue(http.initializeHttps());
6369
}
6470

6571
@Test

0 commit comments

Comments
 (0)