/** * Copyright (c) Cohesive Integrations, LLC * Copyright (c) Codice Foundation * * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. * **/ package net.di2e.ecdr.source.rest; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.is; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import java.net.SocketException; import javax.net.ssl.SSLHandshakeException; import net.di2e.ecdr.libs.cache.impl.MetacardMemoryCacheManager; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.ssl.SslSocketConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ddf.catalog.filter.FilterAdapter; /** * Tests that the certificates are properly added to outgoing requests and allow for mutual authentication on a server * that requires client auth. */ public class CDRRestSourceSecureTest { private static final Logger LOGGER = LoggerFactory.getLogger( CDRRestSourceSecureTest.class ); private static Server server; private static int serverPort = 0; static final String SSL_KEYSTORE_JAVA_PROPERTY = "javax.net.ssl.keyStore"; static final String SSL_KEYSTORE_PASSWORD_JAVA_PROPERTY = "javax.net.ssl.keyStorePassword"; static final String SSL_TRUSTSTORE_JAVA_PROPERTY = "javax.net.ssl.trustStore"; static final String SSL_TRUSTSTORE_PASSWORD_JAVA_PROPERTY = "javax.net.ssl.trustStorePassword"; @BeforeClass public static void startServer() { // create jetty server server = new Server(); server.setStopAtShutdown( true ); ServletContextHandler context = new ServletContextHandler(); context.setContextPath( "/" ); // add dummy servlet that will return static response context.addServlet( TrustedServlet.class, "/" ); HandlerCollection handlers = new HandlerCollection(); handlers.setHandlers( new Handler[] { context, new DefaultHandler() } ); server.setHandler( handlers ); SslContextFactory sslContextFactory = new SslContextFactory(); // server uses the server cert sslContextFactory.setKeyStorePath( CDRRestSourceSecureTest.class.getResource( "/serverKeystore.jks" ).getPath() ); sslContextFactory.setKeyStorePassword( "changeit" ); // only accept connection with proper client certificate sslContextFactory.setNeedClientAuth( true ); SslSocketConnector sslSocketConnector = new SslSocketConnector( sslContextFactory ); sslSocketConnector.setPort( serverPort ); server.addConnector( sslSocketConnector ); try { server.start(); if ( server.getConnectors().length == 1 ) { serverPort = server.getConnectors()[0].getLocalPort(); LOGGER.info( "Server started on Port: {} ", serverPort ); } else { LOGGER.warn( "Got more than one connector back, could not determine correct port for SSL communication." ); } } catch ( Exception e ) { LOGGER.warn( "Could not start jetty server, expecting test failures.", e ); } } /** * Tests that server properly accepts trusted certificates. */ @Test public void testGoodCertificates() { CDROpenSearchSource restSource = createSecuredSource( "/serverKeystore.jks", "changeit", "/serverTruststore.jks", "changeit" ); // quick check that getters do not cause exceptions restSource.getContentTypes(); restSource.getOptions( null ); restSource.getSupportedSchemes(); // hit server if ( restSource.isAvailable() == false ) { fail( "Could not get capabilities from the test server. This means no connection was established." ); } } /** * Tests that server fails on non-trusted client certificates. */ @SuppressWarnings( "unchecked" ) @Test public void testBadClientCertificate() { CDROpenSearchSource restSource = createSecuredSource( "/client-bad.jks", "", "/serverTruststore.jks", "changeit" ); // hit server try { if ( restSource.isAvailable() ) { fail( "Server should have errored out with bad certificate but request passed instead." ); } } catch ( Exception e ) { assertThat( e.getCause(), anyOf( is( SSLHandshakeException.class ), is( SocketException.class ) ) ); } } /** * Tests that client fails on non-trusted server certificates. */ // @Test public void testBadServerCertificate() { CDROpenSearchSource restSource = createSecuredSource( "/serverKeystore.jks", "changeit", "/client-bad.jks", "" ); // hit server try { if ( restSource.isAvailable() ) { fail( "Client should have errored out with no valid certification path found, but request passed instead." ); } } catch ( Exception e ) { assertThat( e.getCause(), is( SSLHandshakeException.class ) ); } } /** * Creates the Rest Source and sets the ping method and no ping caching so it the tests will return the proper value */ private CDROpenSearchSource createSecuredSource( String keyStorePath, String keyStorePassword, String trustStorePath, String trustStorePassword ) { System.setProperty( SSL_KEYSTORE_JAVA_PROPERTY, CDRRestSourceSecureTest.class.getResource( keyStorePath ).getPath() ); System.setProperty( SSL_KEYSTORE_PASSWORD_JAVA_PROPERTY, keyStorePassword ); System.setProperty( SSL_TRUSTSTORE_JAVA_PROPERTY, CDRRestSourceSecureTest.class.getResource( trustStorePath ).getPath() ); System.setProperty( SSL_TRUSTSTORE_PASSWORD_JAVA_PROPERTY, trustStorePassword ); FilterAdapter filterAdapter = mock( FilterAdapter.class ); CDROpenSearchSource source = new CDROpenSearchSource( filterAdapter, new MetacardMemoryCacheManager() ); source.setPingUrl( "https://localhost:" + serverPort + "/" ); source.setPingMethodString( CDRSourceConfiguration.PingMethod.HEAD.toString() ); source.setAvailableCheckCacheTime( 0 ); source.setMaxResultCount( 10 ); source.setUrl( "https://localhost:" + serverPort + "/" ); source.setReceiveTimeoutSeconds( 10 ); source.setConnectionTimeoutSeconds( 1 ); return source; } }