Issue #5019 - add testing for the SslKeyStoreScanner
Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
parent
24d0e50a0c
commit
13e034fc5f
|
@ -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.
|
@ -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.
Loading…
Reference in New Issue