-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue #5019 - add testing for the SslKeyStoreScanner
Signed-off-by: Lachlan Roberts <[email protected]>
- Loading branch information
1 parent
24d0e50
commit 13e034f
Showing
5 changed files
with
216 additions
and
0 deletions.
There are no files selected for viewing
212 changes: 212 additions & 0 deletions
212
jetty-ssl-reload/src/test/java/org/eclipse/jetty/ssl/reload/KeystoreReloadTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
// | ||
// ======================================================================== | ||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others. | ||
// ------------------------------------------------------------------------ | ||
// All rights reserved. This program and the accompanying materials | ||
// are made available under the terms of the Eclipse Public License v1.0 | ||
// and Apache License v2.0 which accompanies this distribution. | ||
// | ||
// The Eclipse Public License is available at | ||
// http://www.eclipse.org/legal/epl-v10.html | ||
// | ||
// The Apache License v2.0 is available at | ||
// http://www.opensource.org/licenses/apache2.0.php | ||
// | ||
// You may elect to redistribute this code under either of these licenses. | ||
// ======================================================================== | ||
// | ||
|
||
package org.eclipse.jetty.ssl.reload; | ||
|
||
import java.net.URL; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.security.SecureRandom; | ||
import java.security.cert.Certificate; | ||
import java.security.cert.X509Certificate; | ||
import java.time.Duration; | ||
import java.util.Calendar; | ||
import javax.net.ssl.HttpsURLConnection; | ||
import javax.net.ssl.KeyManager; | ||
import javax.net.ssl.SSLContext; | ||
import javax.net.ssl.SSLHandshakeException; | ||
import javax.net.ssl.TrustManager; | ||
import javax.net.ssl.X509TrustManager; | ||
|
||
import org.eclipse.jetty.http.HttpVersion; | ||
import org.eclipse.jetty.server.HttpConfiguration; | ||
import org.eclipse.jetty.server.HttpConnectionFactory; | ||
import org.eclipse.jetty.server.SecureRequestCustomizer; | ||
import org.eclipse.jetty.server.Server; | ||
import org.eclipse.jetty.server.ServerConnector; | ||
import org.eclipse.jetty.server.SslConnectionFactory; | ||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; | ||
import org.eclipse.jetty.util.log.StacklessLogging; | ||
import org.eclipse.jetty.util.ssl.SslContextFactory; | ||
import org.junit.jupiter.api.AfterEach; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.is; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
|
||
public class KeystoreReloadTest | ||
{ | ||
private static final int scanInterval = 1; | ||
private Server server; | ||
|
||
public static void useKeystore(String keystore) throws Exception | ||
{ | ||
Path keystoreDir = MavenTestingUtils.getTestResourcePath("keystoreDir"); | ||
Path keystorePath = keystoreDir.resolve("keystore"); | ||
if (Files.exists(keystorePath)) | ||
Files.delete(keystorePath); | ||
|
||
if (keystore == null) | ||
return; | ||
|
||
Files.copy(MavenTestingUtils.getTestResourceFile(keystore).toPath(), keystorePath); | ||
keystorePath.toFile().deleteOnExit(); | ||
} | ||
|
||
@BeforeEach | ||
public void start() throws Exception | ||
{ | ||
useKeystore("oldKeystore"); | ||
|
||
String keystore = MavenTestingUtils.getTestResourceFile("keystoreDir/keystore").getAbsolutePath(); | ||
SslContextFactory sslContextFactory = new SslContextFactory.Server(); | ||
sslContextFactory.setKeyStorePath(keystore); | ||
sslContextFactory.setKeyStorePassword("storepwd"); | ||
sslContextFactory.setKeyManagerPassword("keypwd"); | ||
|
||
server = new Server(); | ||
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()); | ||
HttpConfiguration httpsConfig = new HttpConfiguration(); | ||
httpsConfig.addCustomizer(new SecureRequestCustomizer()); | ||
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpsConfig); | ||
ServerConnector connector = new ServerConnector(server, sslConnectionFactory, httpConnectionFactory); | ||
connector.setPort(8443); | ||
server.addConnector(connector); | ||
|
||
// Configure Keystore Reload. | ||
SslKeyStoreScanner keystoreScanner = new SslKeyStoreScanner(sslContextFactory); | ||
keystoreScanner.setScanInterval(scanInterval); | ||
server.addBean(keystoreScanner); | ||
|
||
server.start(); | ||
} | ||
|
||
@AfterEach | ||
public void stop() throws Exception | ||
{ | ||
server.stop(); | ||
} | ||
|
||
@Test | ||
public void testKeystoreHotReload() throws Exception | ||
{ | ||
URL serverUrl = server.getURI().toURL(); | ||
|
||
// Check the original certificate expiry. | ||
X509Certificate cert1 = getCertificateFromUrl(serverUrl); | ||
assertThat(getExpiryYear(cert1), is(2015)); | ||
|
||
// Switch to use newKeystore which has a later expiry date. | ||
useKeystore("newKeystore"); | ||
Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis()); | ||
|
||
// The scanner should have detected the updated keystore, expiry should be renewed. | ||
X509Certificate cert2 = getCertificateFromUrl(serverUrl); | ||
assertThat(getExpiryYear(cert2), is(2020)); | ||
} | ||
|
||
@Test | ||
public void testReloadWithBadKeystore() throws Exception | ||
{ | ||
URL serverUrl = server.getURI().toURL(); | ||
|
||
// Check the original certificate expiry. | ||
X509Certificate cert1 = getCertificateFromUrl(serverUrl); | ||
assertThat(getExpiryYear(cert1), is(2015)); | ||
|
||
// Switch to use badKeystore which has the incorrect passwords. | ||
try (StacklessLogging ignored = new StacklessLogging(SslKeyStoreScanner.class)) | ||
{ | ||
useKeystore("badKeystore"); | ||
Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis()); | ||
} | ||
|
||
// The good keystore is removed, now the bad keystore now causes an SSL Handshake exception. | ||
assertThrows(SSLHandshakeException.class, () -> getCertificateFromUrl(serverUrl)); | ||
} | ||
|
||
@Test | ||
public void testKeystoreRemoval() throws Exception | ||
{ | ||
URL serverUrl = server.getURI().toURL(); | ||
|
||
// Check the original certificate expiry. | ||
X509Certificate cert1 = getCertificateFromUrl(serverUrl); | ||
assertThat(getExpiryYear(cert1), is(2015)); | ||
|
||
// Delete the keystore. | ||
try (StacklessLogging ignored = new StacklessLogging(SslKeyStoreScanner.class)) | ||
{ | ||
useKeystore(null); | ||
Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis()); | ||
} | ||
|
||
// The good keystore is removed, having no keystore causes an SSL Handshake exception. | ||
assertThrows(SSLHandshakeException.class, () -> getCertificateFromUrl(serverUrl)); | ||
|
||
// Switch to use keystore2 which has a later expiry date. | ||
useKeystore("newKeystore"); | ||
Thread.sleep(Duration.ofSeconds(scanInterval * 2).toMillis()); | ||
X509Certificate cert2 = getCertificateFromUrl(serverUrl); | ||
assertThat(getExpiryYear(cert2), is(2020)); | ||
} | ||
|
||
public static int getExpiryYear(X509Certificate cert) | ||
{ | ||
Calendar instance = Calendar.getInstance(); | ||
instance.setTime(cert.getNotAfter()); | ||
return instance.get(Calendar.YEAR); | ||
} | ||
|
||
public static X509Certificate getCertificateFromUrl(URL serverUrl) throws Exception | ||
{ | ||
SSLContext ctx = SSLContext.getInstance("TLS"); | ||
ctx.init(new KeyManager[0], new TrustManager[] {new DefaultTrustManager()}, new SecureRandom()); | ||
SSLContext.setDefault(ctx); | ||
|
||
HttpsURLConnection connection = (HttpsURLConnection)serverUrl.openConnection(); | ||
connection.setHostnameVerifier((a, b) -> true); | ||
connection.connect(); | ||
Certificate[] certs = connection.getServerCertificates(); | ||
connection.disconnect(); | ||
|
||
assertThat(certs.length, is(1)); | ||
return (X509Certificate)certs[0]; | ||
} | ||
|
||
private static class DefaultTrustManager implements X509TrustManager | ||
{ | ||
@Override | ||
public void checkClientTrusted(X509Certificate[] arg0, String arg1) | ||
{ | ||
} | ||
|
||
@Override | ||
public void checkServerTrusted(X509Certificate[] arg0, String arg1) | ||
{ | ||
} | ||
|
||
@Override | ||
public X509Certificate[] getAcceptedIssuers() | ||
{ | ||
return null; | ||
} | ||
} | ||
} |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog | ||
org.eclipse.jetty.deploy.LEVEL=WARN | ||
org.eclipse.jetty.util.Scanner=WARN | ||
org.eclipse.jetty.ssl.reload.LEVEL=DEBUG |
Binary file not shown.
Binary file not shown.