Skip to content

Commit 30b9f2c

Browse files
committed
Adds HTTPS support via new SecureClientDriverRule
1 parent 68775b5 commit 30b9f2c

File tree

7 files changed

+540
-0
lines changed

7 files changed

+540
-0
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.github.restdriver.clientdriver;
2+
3+
import java.security.KeyStore;
4+
5+
import org.eclipse.jetty.util.ssl.SslContextFactory;
6+
7+
import com.github.restdriver.clientdriver.jetty.ClientDriverJettyHandler;
8+
9+
/**
10+
* Secure client driver extends the {@link ClientDriver} to use secure
11+
* connections via HTTPS with a provided certificate.
12+
*/
13+
public class SecureClientDriver extends ClientDriver {
14+
15+
KeyStore keyStore;
16+
String password;
17+
String certificateAlias;
18+
19+
/**
20+
* Constructor which uses the given port to bind to. The server is started
21+
* during construction.
22+
*
23+
* @param handler
24+
* the {@link ClientDriverJettyHandler} to use.
25+
* @param port
26+
* the port to bind to.
27+
* @param keyStore
28+
* the key store to use for the certificate.
29+
* @param password
30+
* the password for the certificate.
31+
* @param certificateAlias
32+
* the alias of the certificate.
33+
*/
34+
public SecureClientDriver(ClientDriverJettyHandler handler, int port, KeyStore keyStore, String password,
35+
String certificateAlias) {
36+
super();
37+
this.keyStore = keyStore;
38+
this.password = password;
39+
this.certificateAlias = certificateAlias;
40+
41+
this.handler = handler;
42+
this.jettyServer = createAndStartJetty(port);
43+
}
44+
45+
/**
46+
* Constructor which uses a free port to bind to. The server is started
47+
* during construction.
48+
*
49+
* @param handler
50+
* the {@link ClientDriverJettyHandler} to use.
51+
* @param keyStore
52+
* the key store to use for the certificate.
53+
* @param password
54+
* the password for the certificate.
55+
* @param certificateAlias
56+
* the alias of the certificate.
57+
*/
58+
public SecureClientDriver(ClientDriverJettyHandler handler, KeyStore keyStore, String password,
59+
String certificateAlias) {
60+
this(handler, 0, keyStore, password, certificateAlias);
61+
}
62+
63+
@Override
64+
protected SslContextFactory getSslContextFactory() {
65+
SslContextFactory sslContextFactoryFactory = new SslContextFactory();
66+
sslContextFactoryFactory.setKeyStore(keyStore);
67+
sslContextFactoryFactory.setCertAlias(certificateAlias);
68+
sslContextFactoryFactory.setKeyStorePassword(password);
69+
sslContextFactoryFactory.setKeyManagerPassword(password);
70+
sslContextFactoryFactory.checkKeyStore();
71+
72+
return sslContextFactoryFactory;
73+
}
74+
75+
@Override
76+
public String getBaseUrl() {
77+
return "https://localhost:" + getPort();
78+
}
79+
80+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package com.github.restdriver.clientdriver;
2+
3+
import java.security.KeyStore;
4+
5+
import org.apache.commons.lang.Validate;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
9+
import com.github.restdriver.clientdriver.jetty.DefaultClientDriverJettyHandler;
10+
11+
/**
12+
* Factory to create a {@link ClientDriver} object which supports SSL. To
13+
* arrange that a key store has to be provided. The required password has to be
14+
* the password of the certificate's private key as well as of the key store.
15+
* This is usually the case if the certificate is exported via OpenSSL e.g. as
16+
* PKCS#12 format.
17+
*
18+
* An example to create a certificate with keytool provided in the JDK:
19+
*
20+
* <pre>
21+
* {@code $ keytool -genkey -keyalg RSA -dname "cn=SecureClientDriver" \
22+
* -alias certificate -keystore keystore.jks -keypass password \
23+
* -storepass password -validity 360 -keysize 2048}
24+
* </pre>
25+
*/
26+
public class SecureClientDriverFactory {
27+
28+
private static final Logger LOGGER = LoggerFactory.getLogger(ClientDriverFactory.class);
29+
30+
private int port = 0;
31+
private String password;
32+
private String certAlias;
33+
private KeyStore keyStore;
34+
35+
/**
36+
* Factory method to create and start ClientDriver. The port will be chosen
37+
* automatically.
38+
*
39+
* @param keyStore
40+
* The {@link KeyStore} that contains the necessary certificate.
41+
* @param password
42+
* The password for the certificate private key & the container.
43+
* @param certAlias
44+
* the alias of the certificate in the key store
45+
* @return a ClientDriver object
46+
*/
47+
public SecureClientDriver createClientDriver(KeyStore keyStore, String password, String certAlias) {
48+
this.password = password;
49+
this.certAlias = certAlias;
50+
this.keyStore = keyStore;
51+
return this.build();
52+
}
53+
54+
/**
55+
* Factory method to create and start a ClientDriver on a specific port. The
56+
* port can be set, the following arguments are to setup SSL.
57+
*
58+
* @param port
59+
* The port that should be used.
60+
* @param keyStore
61+
* The {@link KeyStore} that contains the necessary certificate.
62+
* @param password
63+
* The password for the certificate private key & the container.
64+
* @param certAlias
65+
* the alias of the certificate in the key store
66+
* @return a ClientDriver object
67+
*/
68+
public SecureClientDriver createClientDriver(int port, KeyStore keyStore, String password, String certAlias) {
69+
this.port = port;
70+
this.password = password;
71+
this.certAlias = certAlias;
72+
this.keyStore = keyStore;
73+
return this.build();
74+
}
75+
76+
/**
77+
* Sets the port. By default the port is set to 0, which results in a freely
78+
* chosen port.
79+
*
80+
* @param port
81+
* the port
82+
* @return the factory object
83+
*/
84+
public SecureClientDriverFactory port(int port) {
85+
this.port = port;
86+
return this;
87+
}
88+
89+
/**
90+
* Sets the password. Password may not be null or empty when building.
91+
*
92+
* @param port
93+
* the port
94+
* @return the factory object
95+
*/
96+
public SecureClientDriverFactory password(String password) {
97+
this.password = password;
98+
return this;
99+
}
100+
101+
public SecureClientDriverFactory certAlias(String certAlias) {
102+
this.certAlias = certAlias;
103+
return this;
104+
}
105+
106+
public SecureClientDriverFactory keyStore(KeyStore keyStore) {
107+
this.keyStore = keyStore;
108+
return this;
109+
}
110+
111+
/**
112+
* Create SecureClientDriver with the given configuration.
113+
*
114+
* @return
115+
*/
116+
public SecureClientDriver build() {
117+
Validate.notEmpty(certAlias, "Certificate alias is not set.");
118+
Validate.notEmpty(password, "Password not set.");
119+
Validate.notNull(keyStore, "Key store is not set.");
120+
SecureClientDriver clientDriver = new SecureClientDriver(
121+
new DefaultClientDriverJettyHandler(new DefaultRequestMatcher()), port, keyStore, password, certAlias);
122+
LOGGER.debug("ClientDriver created at '" + clientDriver.getBaseUrl() + "'.");
123+
return clientDriver;
124+
}
125+
126+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.github.restdriver.clientdriver;
2+
3+
import java.security.KeyStore;
4+
5+
/**
6+
* The SecureClientDriverRule allows a user to specify expectations on the HTTPS
7+
* requests that are made against it.
8+
*/
9+
public class SecureClientDriverRule extends ClientDriverRule {
10+
11+
/**
12+
* Creates a new rule which binds the driver to a free port.
13+
*
14+
* @param keyStore
15+
* the key store with the certificate
16+
* @param password
17+
* the certificate's password
18+
* @param certAlias
19+
* the alias of the certificate
20+
*/
21+
public SecureClientDriverRule(KeyStore keyStore, String password, String certAlias) {
22+
clientDriver = new SecureClientDriverFactory().createClientDriver(keyStore, password, certAlias);
23+
}
24+
25+
/**
26+
* Creates a new rule which binds the driver to a free port.
27+
*
28+
* @param keyStore
29+
* the key store with the certificate
30+
* @param password
31+
* the certificate's password
32+
* @param certAlias
33+
* the alias of the certificate
34+
*/
35+
public SecureClientDriverRule(int port, KeyStore keyStore, String password, String certAlias) {
36+
clientDriver = new SecureClientDriverFactory().createClientDriver(port, keyStore, password, certAlias);
37+
}
38+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.github.restdriver.clientdriver.integration;
2+
3+
import static com.github.restdriver.clientdriver.RestClientDriver.giveEmptyResponse;
4+
import static com.github.restdriver.clientdriver.RestClientDriver.onRequestTo;
5+
import static org.junit.Assert.*;
6+
7+
import java.io.ByteArrayInputStream;
8+
import java.security.KeyManagementException;
9+
import java.security.KeyStore;
10+
import java.security.KeyStoreException;
11+
import java.security.NoSuchAlgorithmException;
12+
13+
import javax.net.ssl.SSLContext;
14+
15+
import org.apache.commons.io.IOUtils;
16+
import org.apache.http.HttpResponse;
17+
import org.apache.http.client.HttpClient;
18+
import org.apache.http.client.methods.HttpGet;
19+
import org.apache.http.conn.ssl.NoopHostnameVerifier;
20+
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
21+
import org.apache.http.impl.client.HttpClients;
22+
import org.apache.http.ssl.SSLContexts;
23+
import org.junit.Rule;
24+
import org.junit.Test;
25+
import org.junit.rules.ExpectedException;
26+
27+
import com.github.restdriver.clientdriver.SecureClientDriverRule;
28+
import com.github.restdriver.clientdriver.exception.ClientDriverFailedExpectationException;
29+
import com.github.restdriver.clientdriver.exception.ClientDriverSetupException;
30+
31+
public class SecureClientDriverRuleTest {
32+
33+
@Rule
34+
public SecureClientDriverRule rule = new SecureClientDriverRule(getKeystore(), "password", "certificate");
35+
@Rule
36+
public ExpectedException thrown = ExpectedException.none();
37+
38+
@Test
39+
public void usageOfRuleWithMatchingCallSucceeds() throws Exception {
40+
41+
// Arrange
42+
rule.addExpectation(onRequestTo("/test"), giveEmptyResponse());
43+
44+
HttpClient client = getClient();
45+
HttpGet getter = new HttpGet(rule.getBaseUrl() + "/test");
46+
47+
// Act
48+
HttpResponse response = client.execute(getter);
49+
50+
// Assert
51+
assertEquals(204, response.getStatusLine().getStatusCode());
52+
}
53+
54+
@Test
55+
public void usageOfRuleWithNotMatchingCallFails() throws Exception {
56+
57+
// Arrange
58+
rule.addExpectation(onRequestTo("/test"), giveEmptyResponse());
59+
60+
HttpClient client = getClient();
61+
HttpGet getter = new HttpGet(rule.getBaseUrl() + "/wrong");
62+
// Act
63+
client.execute(getter);
64+
// Assert
65+
thrown.expect(ClientDriverFailedExpectationException.class);
66+
}
67+
68+
@Test
69+
public void usageOfRuleWithNotCallFails() throws Exception {
70+
71+
// Arrange
72+
rule.addExpectation(onRequestTo("/test"), giveEmptyResponse());
73+
74+
// Act
75+
76+
// Assert
77+
thrown.expect(ClientDriverFailedExpectationException.class);
78+
}
79+
80+
private static KeyStore getKeystore() {
81+
try {
82+
ClassLoader loader = SecureClientDriverTest.class.getClassLoader();
83+
byte[] binaryContent = IOUtils.toByteArray(loader.getResourceAsStream("keystore.jks"));
84+
KeyStore keyStore = KeyStore.getInstance("JKS");
85+
keyStore.load(new ByteArrayInputStream(binaryContent), "password".toCharArray());
86+
return keyStore;
87+
} catch (Exception e) {
88+
throw new ClientDriverSetupException("Key store could not be loaded.", e);
89+
}
90+
}
91+
92+
private HttpClient getClient() throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
93+
try {
94+
// set the test certificate as trusted
95+
SSLContext context = SSLContexts.custom().loadTrustMaterial(getKeystore(), TrustSelfSignedStrategy.INSTANCE)
96+
.build();
97+
return HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).setSSLContext(context)
98+
.build();
99+
} catch (Exception e) {
100+
throw new ClientDriverSetupException("Client could not be created.", e);
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)