Skip to content

Commit ef06412

Browse files
committed
Added tests for HTTP SSL.
1 parent 7d90b30 commit ef06412

File tree

3 files changed

+516
-1
lines changed

3 files changed

+516
-1
lines changed

rexster-server/src/integration/java/com/tinkerpop/rexster/AbstractResourceIntegrationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ private static void clean() {
206206
removeDirectory(new File("/tmp/rexster-integration-tests"));
207207
}
208208

209-
private static boolean removeDirectory(final File directory) {
209+
public static boolean removeDirectory(final File directory) {
210210
if (directory == null)
211211
return false;
212212
if (!directory.exists())
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
package com.tinkerpop.rexster.ssl;
2+
3+
import static org.junit.Assert.fail;
4+
import static javax.ws.rs.HttpMethod.GET;
5+
6+
import com.sun.jersey.api.client.Client;
7+
import com.sun.jersey.api.client.ClientHandlerException;
8+
import com.sun.jersey.api.client.ClientRequest;
9+
import com.sun.jersey.api.client.config.ClientConfig;
10+
import com.sun.jersey.api.client.config.DefaultClientConfig;
11+
import com.sun.jersey.client.urlconnection.HTTPSProperties;
12+
import com.tinkerpop.rexster.AbstractResourceIntegrationTest;
13+
import com.tinkerpop.rexster.Application;
14+
import com.tinkerpop.rexster.Tokens;
15+
import com.tinkerpop.rexster.server.HttpRexsterServer;
16+
import com.tinkerpop.rexster.server.RexsterApplication;
17+
import com.tinkerpop.rexster.server.RexsterServer;
18+
import com.tinkerpop.rexster.server.XmlRexsterApplication;
19+
20+
import org.apache.commons.configuration.HierarchicalConfiguration;
21+
import org.apache.commons.configuration.XMLConfiguration;
22+
import org.junit.After;
23+
import org.junit.Before;
24+
import org.junit.Test;
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
28+
import java.io.BufferedReader;
29+
import java.io.File;
30+
import java.io.FileInputStream;
31+
import java.io.IOException;
32+
import java.io.InputStream;
33+
import java.io.InputStreamReader;
34+
import java.net.SocketException;
35+
import java.net.URI;
36+
import java.security.KeyStore;
37+
import java.security.KeyStoreException;
38+
import java.security.NoSuchAlgorithmException;
39+
import java.security.cert.CertificateException;
40+
import java.util.List;
41+
42+
import javax.net.ssl.HostnameVerifier;
43+
import javax.net.ssl.KeyManagerFactory;
44+
import javax.net.ssl.SSLContext;
45+
import javax.net.ssl.SSLHandshakeException;
46+
import javax.net.ssl.SSLSession;
47+
import javax.net.ssl.TrustManagerFactory;
48+
49+
/**
50+
* Tests Rexster HTTP SSL support. Note that the same keystore is used as both keystore and truststore for brevity, this
51+
* shouldn't be done in production. See {@code rexster-integration-test-ssl.xml} in the test resources dir for the SSL
52+
* configuration being used by Rexster.
53+
*/
54+
public class RexsterHttpSslTest {
55+
private static final Logger logger = LoggerFactory.getLogger(RexsterHttpSslTest.class);
56+
private static final String REXSTER_INTEGRATION_TEST_DIR = "/tmp/rexster-integration-tests";
57+
private static final String CLIENT_KEYSTORE_PATH = "clientSslKeys.jks";
58+
private static final String UNTRUSTED_KEYSTORE = CLIENT_KEYSTORE_PATH + "_untrusted";
59+
private static final String SERVER_KEYSTORE_PATH = "serverSslKeys.jks";
60+
private static final String CLIENT_CERT = "client.cert";
61+
private static final String SERVER_CERT = "server.cert";
62+
private static final String BASE_URI = "https://127.0.0.1:8182";
63+
private static final URI GRAPHS_URI = URI.create(BASE_URI + "/graphs");
64+
private static final String REXSTER_SSL_BASE_CONFIGURATION = "rexster-integration-test-ssl.xml";
65+
private static final String PASSWORD = "password";
66+
67+
private RexsterServer rexsterServer;
68+
private final ClientConfig clientConfiguration = new DefaultClientConfig();
69+
private Client client;
70+
71+
@Before
72+
public void setUp() throws Exception {
73+
clean();
74+
75+
new File(REXSTER_INTEGRATION_TEST_DIR).mkdirs();
76+
buildSslKeys();
77+
78+
final XMLConfiguration properties = new XMLConfiguration();
79+
properties.load(Application.class.getResourceAsStream(REXSTER_SSL_BASE_CONFIGURATION));
80+
81+
rexsterServer = new HttpRexsterServer(properties);
82+
final List<HierarchicalConfiguration> graphConfigs = properties.configurationsAt(Tokens.REXSTER_GRAPH_PATH);
83+
final RexsterApplication application = new XmlRexsterApplication(graphConfigs);
84+
rexsterServer.start(application);
85+
86+
client = Client.create(clientConfiguration);
87+
}
88+
89+
@After
90+
public void tearDown() throws Exception {
91+
rexsterServer.stop();
92+
}
93+
94+
@Test
95+
public void testSslExceptionOccursIfSslIsEnabledForServerAndClientDoesntTrustServer()
96+
throws NoSuchAlgorithmException {
97+
final HTTPSProperties httpsProperties = new HTTPSProperties(DONT_VERIFY_HOSTNAME, SSLContext.getDefault());
98+
client.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, httpsProperties);
99+
100+
// should fail because the client doesn't trust the server:
101+
final ClientRequest graphRequest = ClientRequest.create().build(GRAPHS_URI, GET);
102+
try {
103+
this.client.handle(graphRequest);
104+
fail("Expected exception did not occur.");
105+
} catch (ClientHandlerException e) {
106+
if (!e.getCause().getClass().equals(SSLHandshakeException.class)) {
107+
fail("Unexpected exception.");
108+
}
109+
}
110+
}
111+
112+
@Test
113+
@SuppressWarnings({"JUnitTestMethodWithNoAssertions"})
114+
public void testOneWaySslWorksWhenClientTrustsServer() throws Exception {
115+
final SSLContext sslContext = SSLContext.getInstance("TLS");
116+
final TrustManagerFactory trustManagerFactory = initAndGetClientTrustManagerFactory(CLIENT_KEYSTORE_PATH);
117+
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
118+
119+
final HTTPSProperties httpsProperties = new HTTPSProperties(DONT_VERIFY_HOSTNAME, sslContext);
120+
client.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, httpsProperties);
121+
122+
// client trusts server, this should succeed:
123+
final ClientRequest graphRequest = ClientRequest.create().build(GRAPHS_URI, GET);
124+
this.client.handle(graphRequest);
125+
}
126+
127+
@Test
128+
public void testRequireClientAuthRequestThrowsSocketExceptionIfServerDoesntTrustClient() throws Exception {
129+
reinitializeRexsterWithRequireClientAuth();
130+
131+
final SSLContext sslContext = SSLContext.getInstance("TLS");
132+
final TrustManagerFactory trustManagerFactory = initAndGetClientTrustManagerFactory(UNTRUSTED_KEYSTORE);
133+
final KeyManagerFactory keyManagerFactory = initAndGetClientKeyManagerFactory(UNTRUSTED_KEYSTORE);
134+
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
135+
136+
final HTTPSProperties httpsProperties = new HTTPSProperties(DONT_VERIFY_HOSTNAME, sslContext);
137+
client.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, httpsProperties);
138+
139+
final ClientRequest graphRequest = ClientRequest.create().build(GRAPHS_URI, GET);
140+
141+
// server should throw an SSLHandshakeException internally and reset the connection:
142+
try {
143+
this.client.handle(graphRequest);
144+
fail("Expected exception did not occur.");
145+
} catch (ClientHandlerException e) {
146+
if (!e.getCause().getClass().equals(SocketException.class)) {
147+
fail("Unexpected exception.");
148+
}
149+
}
150+
}
151+
152+
@Test
153+
@SuppressWarnings({"JUnitTestMethodWithNoAssertions"})
154+
public void testRequireClientAuthWorksWhenServerTrustsClient() throws Exception {
155+
reinitializeRexsterWithRequireClientAuth();
156+
157+
final SSLContext sslContext = SSLContext.getInstance("TLS");
158+
final TrustManagerFactory trustManagerFactory = initAndGetClientTrustManagerFactory(CLIENT_KEYSTORE_PATH);
159+
final KeyManagerFactory keyManagerFactory = initAndGetClientKeyManagerFactory(CLIENT_KEYSTORE_PATH);
160+
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
161+
162+
final HTTPSProperties httpsProperties = new HTTPSProperties(DONT_VERIFY_HOSTNAME, sslContext);
163+
client.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, httpsProperties);
164+
165+
final ClientRequest graphRequest = ClientRequest.create().build(GRAPHS_URI, GET);
166+
167+
// client and server both trust each other, this should succeed:
168+
this.client.handle(graphRequest);
169+
}
170+
171+
/**
172+
* Restarts the Rexster server and configures its SSL to require client authentication.
173+
*/
174+
private void reinitializeRexsterWithRequireClientAuth() throws Exception {
175+
rexsterServer.stop();
176+
final XMLConfiguration properties = new XMLConfiguration();
177+
properties.load(Application.class.getResourceAsStream(REXSTER_SSL_BASE_CONFIGURATION));
178+
properties.setProperty("ssl.need-client-auth", "true");
179+
properties.setProperty("ssl.want-client-auth", "true");
180+
181+
rexsterServer = new HttpRexsterServer(properties);
182+
183+
final List<HierarchicalConfiguration> graphConfigs = properties.configurationsAt(Tokens.REXSTER_GRAPH_PATH);
184+
final RexsterApplication application = new XmlRexsterApplication(graphConfigs);
185+
rexsterServer.start(application);
186+
}
187+
188+
/**
189+
* Generates a {@code TrustManagerFactory} that provides trust for the Rexster server.
190+
*
191+
* @return a {@code TrustManagerFactory} that can be used for SSL operations and trusts the Rexster server
192+
*/
193+
private static TrustManagerFactory initAndGetClientTrustManagerFactory(String pathToStore)
194+
throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
195+
final TrustManagerFactory trustManagerFactory =
196+
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
197+
198+
final KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
199+
200+
FileInputStream trustStoreInputStream = null;
201+
try {
202+
trustStoreInputStream = new FileInputStream(REXSTER_INTEGRATION_TEST_DIR + '/' + pathToStore);
203+
trustStore.load(trustStoreInputStream, PASSWORD.toCharArray());
204+
} finally {
205+
if (trustStoreInputStream != null) {
206+
trustStoreInputStream.close();
207+
}
208+
}
209+
trustManagerFactory.init(trustStore);
210+
211+
return trustManagerFactory;
212+
}
213+
214+
/**
215+
* Generates a {@code KeyManagerFactory} loaded with the key store at the given path.
216+
*
217+
* @param keyStorePath path to the keystore with which to load the returned {@code KeyManagerFactory}
218+
* @return a {@code KeyManagerFactory} that can be used for SSL operations
219+
*/
220+
private static KeyManagerFactory initAndGetClientKeyManagerFactory(final String keyStorePath) throws Exception {
221+
final KeyManagerFactory keyManagerFactory =
222+
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
223+
224+
final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
225+
FileInputStream keyStoreInputStream = null;
226+
try {
227+
keyStoreInputStream = new FileInputStream(REXSTER_INTEGRATION_TEST_DIR + '/' + keyStorePath);
228+
keyStore.load(keyStoreInputStream, PASSWORD.toCharArray());
229+
} finally {
230+
if (keyStoreInputStream != null) {
231+
keyStoreInputStream.close();
232+
}
233+
}
234+
keyManagerFactory.init(keyStore, PASSWORD.toCharArray());
235+
236+
return keyManagerFactory;
237+
}
238+
239+
/**
240+
* Generates SSL keys for the client and server. Imports the client cert into the server keystore and the server
241+
* cert into the client keystore. Also generates a client keystore that imports the sever cert but doesnt have its
242+
* cert imported into the server keystore - the 'untrusted client'. Note that a single keystore serves as both
243+
* keystore and trust store in these tests
244+
*/
245+
private static void buildSslKeys() throws IOException, InterruptedException {
246+
final String[] generateClientKeyStore =
247+
{"keytool", "-genkey", "-v", "-alias", "client", "-keypass", PASSWORD, "-keystore",
248+
CLIENT_KEYSTORE_PATH, "-storepass", PASSWORD, "-storetype", "jks", "-dname",
249+
"CN=client, O=client, C=US", "-keyalg", "RSA"};
250+
251+
final String[] exportClientCertificate =
252+
{"keytool", "-export", "-v", "-alias", "client", "-file", CLIENT_CERT, "-rfc", "-keystore",
253+
CLIENT_KEYSTORE_PATH, "-storepass", PASSWORD, "-storetype", "jks"};
254+
255+
final String[] generateServerKeyStore =
256+
{"keytool", "-genkey", "-v", "-alias", "server", "-keypass", PASSWORD, "-keystore",
257+
SERVER_KEYSTORE_PATH, "-storepass", PASSWORD, "-storetype", "jks", "-dname",
258+
"CN=server, O=server, C=US", "-keyalg", "RSA"};
259+
260+
final String[] exportServerCertificate =
261+
{"keytool", "-export", "-v", "-alias", "server", "-file", SERVER_CERT, "-rfc", "-keystore",
262+
SERVER_KEYSTORE_PATH, "-storepass", PASSWORD, "-storetype", "jks"};
263+
264+
final String[] importServerCertToClient =
265+
{"keytool", "-import", "-v", "-alias", "server", "-noprompt", "-file", SERVER_CERT, "-keystore",
266+
CLIENT_KEYSTORE_PATH, "-storepass", PASSWORD, "-storetype", "jks"};
267+
268+
final String[] importClientCertToServer =
269+
{"keytool", "-import", "-v", "-alias", "client", "-noprompt", "-file", CLIENT_CERT, "-keystore",
270+
SERVER_KEYSTORE_PATH, "-storepass", PASSWORD, "-storetype", "jks"};
271+
272+
final String[] generateUntrustedClientKeys =
273+
{"keytool", "-genkey", "-v", "-alias", "untrusted", "-keypass", PASSWORD, "-keystore",
274+
UNTRUSTED_KEYSTORE, "-storepass", PASSWORD, "-storetype", "jks", "-dname",
275+
"CN=untrusted, O=client, C=US", "-keyalg", "RSA"};
276+
277+
final String[] importServerCertToUntrustedClientKeys =
278+
{"keytool", "-import", "-v", "-alias", "server", "-noprompt", "-file", SERVER_CERT, "-keystore",
279+
UNTRUSTED_KEYSTORE, "-storepass", PASSWORD, "-storetype", "jks"};
280+
281+
final String[][] keystoreGenerationCommands =
282+
{generateClientKeyStore, exportClientCertificate, generateServerKeyStore, exportServerCertificate,
283+
importServerCertToClient, importClientCertToServer, generateUntrustedClientKeys,
284+
importServerCertToUntrustedClientKeys};
285+
286+
for (final String[] command : keystoreGenerationCommands) {
287+
final ProcessBuilder pb = new ProcessBuilder();
288+
pb.command(command);
289+
pb.directory(new File(REXSTER_INTEGRATION_TEST_DIR));
290+
pb.redirectErrorStream();
291+
292+
final Process process = pb.start();
293+
final InputStream sterr = process.getErrorStream();
294+
295+
final BufferedReader reader = new BufferedReader(new InputStreamReader(sterr));
296+
try {
297+
String line;
298+
while ((line = reader.readLine()) != null) {
299+
logger.debug(line);
300+
}
301+
} finally {
302+
reader.close();
303+
}
304+
process.waitFor();
305+
}
306+
}
307+
308+
/**
309+
* Deletes the test directory where we store the SSL keys used for testing.
310+
*/
311+
private static void clean() {
312+
AbstractResourceIntegrationTest.removeDirectory(new File(REXSTER_INTEGRATION_TEST_DIR));
313+
}
314+
315+
/**
316+
* Ignoring host name verification by using this {@code HostnameVerifier}.
317+
*/
318+
private static final HostnameVerifier DONT_VERIFY_HOSTNAME = new HostnameVerifier() {
319+
@Override
320+
public boolean verify(String s, SSLSession sslSession) {
321+
return true;
322+
}
323+
};
324+
}

0 commit comments

Comments
 (0)