diff --git a/CHANGELOG.md b/CHANGELOG.md
index 18f4095..febc0a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.8.0 [unreleased]
+* **[breaking]** Removed support for `HTTPVaultConnectorFactory#withSslContext()` in favor of `#withTrustedCA()` due to
+refactoring of the internal SSL handling.
+* [improvement] `VaultConnector` extends `java.io.Serializable`
+
## 0.7.1 [2018-03-17]
* [improvement] Added automatic module name for JPMS compatibility
* [dependencies] Minor dependency updates
diff --git a/pom.xml b/pom.xml
index d471d67..2e7c9e9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
de.stklcode.jvault
connector
- 0.7.1
+ 0.8.0-SNAPSHOT
jar
diff --git a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java
index ad2f22c..d00b1fd 100644
--- a/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java
+++ b/src/main/java/de/stklcode/jvault/connector/HTTPVaultConnector.java
@@ -29,18 +29,23 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -74,14 +79,14 @@ public class HTTPVaultConnector implements VaultConnector {
private final ObjectMapper jsonMapper;
- private final String baseURL; /* Base URL of Vault */
- private final SSLContext sslContext; /* Custom SSLSocketFactory */
- private final int retries; /* Number of retries on 5xx errors */
- private final Integer timeout; /* Timeout in milliseconds */
+ private final String baseURL; // Base URL of Vault.
+ private final X509Certificate trustedCaCert; // Trusted CA certificate.
+ private final int retries; // Number of retries on 5xx errors.
+ private final Integer timeout; // Timeout in milliseconds.
- private boolean authorized = false; /* authorization status */
- private String token; /* current token */
- private long tokenTTL = 0; /* expiration time for current token */
+ private boolean authorized = false; // Authorization status.
+ private String token; // Current token.
+ private long tokenTTL = 0; // Expiration time for current token.
/**
* Create connector using hostname and schema.
@@ -122,18 +127,18 @@ public class HTTPVaultConnector implements VaultConnector {
/**
* Create connector using hostname, schema, port, path and trusted certificate.
*
- * @param hostname The hostname
- * @param useTLS If TRUE, use HTTPS, otherwise HTTP
- * @param port The port
- * @param prefix HTTP API prefix (default: /v1/)
- * @param sslContext Custom SSL Context
+ * @param hostname The hostname
+ * @param useTLS If TRUE, use HTTPS, otherwise HTTP
+ * @param port The port
+ * @param prefix HTTP API prefix (default: /v1/)
+ * @param trustedCaCert Trusted CA certificate
*/
public HTTPVaultConnector(final String hostname,
final boolean useTLS,
final Integer port,
final String prefix,
- final SSLContext sslContext) {
- this(hostname, useTLS, port, prefix, sslContext, 0, null);
+ final X509Certificate trustedCaCert) {
+ this(hostname, useTLS, port, prefix, trustedCaCert, 0, null);
}
/**
@@ -143,7 +148,7 @@ public class HTTPVaultConnector implements VaultConnector {
* @param useTLS If TRUE, use HTTPS, otherwise HTTP
* @param port The port
* @param prefix HTTP API prefix (default: /v1/)
- * @param sslContext Custom SSL Context
+ * @param trustedCaCert Trusted CA certificate
* @param numberOfRetries Number of retries on 5xx errors
* @param timeout Timeout for HTTP requests (milliseconds)
*/
@@ -151,14 +156,14 @@ public class HTTPVaultConnector implements VaultConnector {
final boolean useTLS,
final Integer port,
final String prefix,
- final SSLContext sslContext,
+ final X509Certificate trustedCaCert,
final int numberOfRetries,
final Integer timeout) {
this(((useTLS) ? "https" : "http")
+ "://" + hostname
+ ((port != null) ? ":" + port : "")
+ prefix,
- sslContext,
+ trustedCaCert,
numberOfRetries,
timeout);
}
@@ -175,38 +180,38 @@ public class HTTPVaultConnector implements VaultConnector {
/**
* Create connector using full URL and trusted certificate.
*
- * @param baseURL The URL
- * @param sslContext Custom SSL Context
+ * @param baseURL The URL
+ * @param trustedCaCert Trusted CA certificate
*/
- public HTTPVaultConnector(final String baseURL, final SSLContext sslContext) {
- this(baseURL, sslContext, 0, null);
+ public HTTPVaultConnector(final String baseURL, final X509Certificate trustedCaCert) {
+ this(baseURL, trustedCaCert, 0, null);
}
/**
* Create connector using full URL and trusted certificate.
*
* @param baseURL The URL
- * @param sslContext Custom SSL Context
+ * @param trustedCaCert Trusted CA certificate
* @param numberOfRetries Number of retries on 5xx errors
*/
- public HTTPVaultConnector(final String baseURL, final SSLContext sslContext, final int numberOfRetries) {
- this(baseURL, sslContext, numberOfRetries, null);
+ public HTTPVaultConnector(final String baseURL, final X509Certificate trustedCaCert, final int numberOfRetries) {
+ this(baseURL, trustedCaCert, numberOfRetries, null);
}
/**
* Create connector using full URL and trusted certificate.
*
* @param baseURL The URL
- * @param sslContext Custom SSL Context
+ * @param trustedCaCert Trusted CA certificate
* @param numberOfRetries Number of retries on 5xx errors
* @param timeout Timeout for HTTP requests (milliseconds)
*/
public HTTPVaultConnector(final String baseURL,
- final SSLContext sslContext,
+ final X509Certificate trustedCaCert,
final int numberOfRetries,
final Integer timeout) {
this.baseURL = baseURL;
- this.sslContext = sslContext;
+ this.trustedCaCert = trustedCaCert;
this.retries = numberOfRetries;
this.timeout = timeout;
this.jsonMapper = new ObjectMapper();
@@ -818,8 +823,11 @@ public class HTTPVaultConnector implements VaultConnector {
/* Set JSON Header */
base.addHeader("accept", "application/json");
- HttpResponse response = null;
- try (CloseableHttpClient httpClient = HttpClientBuilder.create().setSSLContext(sslContext).build()) {
+ CloseableHttpResponse response = null;
+
+ try (CloseableHttpClient httpClient = HttpClientBuilder.create()
+ .setSSLSocketFactory(createSSLSocketFactory())
+ .build()) {
/* Set custom timeout, if defined */
if (this.timeout != null)
base.setConfig(RequestConfig.copy(RequestConfig.DEFAULT).setConnectTimeout(timeout).build());
@@ -890,7 +898,7 @@ public class HTTPVaultConnector implements VaultConnector {
new InputStreamReader(response.getEntity().getContent()))) {
String responseString = br.lines().collect(Collectors.joining("\n"));
ErrorResponse er = jsonMapper.readValue(responseString, ErrorResponse.class);
- /* Check for "permission denied" response */
+ /* Check for "permission denied" response */
if (!er.getErrors().isEmpty() && er.getErrors().get(0).equals("permission denied"))
throw new PermissionDeniedException();
throw new InvalidResponseException(Error.RESPONSE_CODE,
@@ -901,6 +909,39 @@ public class HTTPVaultConnector implements VaultConnector {
}
}
+ /**
+ * Create a custom socket factory from trusted CA certificate.
+ *
+ * @return The factory.
+ * @throws TlsException An error occured during initialization of the SSL context.
+ * @since 0.8.0
+ */
+ private SSLConnectionSocketFactory createSSLSocketFactory() throws TlsException {
+ try {
+ // Create Keystore with trusted certificate.
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keyStore.load(null, null);
+ keyStore.setCertificateEntry("trustedCert", trustedCaCert);
+
+ // Initialize TrustManager.
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(keyStore);
+
+ // Create context usint this TrustManager.
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, tmf.getTrustManagers(), new SecureRandom());
+
+ return new SSLConnectionSocketFactory(
+ context,
+ null,
+ null,
+ SSLConnectionSocketFactory.getDefaultHostnameVerifier()
+ );
+ } catch (CertificateException | NoSuchAlgorithmException | KeyStoreException | IOException | KeyManagementException e) {
+ throw new TlsException(Error.INIT_SSL_CONTEXT, e);
+ }
+ }
+
/**
* Inner class to bundle common error messages.
*/
@@ -910,6 +951,7 @@ public class HTTPVaultConnector implements VaultConnector {
private static final String UNEXPECTED_RESPONSE = "Received response where none was expected";
private static final String URI_FORMAT = "Invalid URI format";
private static final String RESPONSE_CODE = "Invalid response code";
+ private static final String INIT_SSL_CONTEXT = "Unable to intialize SSLContext";
/**
* Constructor hidden, this class should not be instantiated.
diff --git a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java
index 92c1483..c6b48ac 100644
--- a/src/main/java/de/stklcode/jvault/connector/VaultConnector.java
+++ b/src/main/java/de/stklcode/jvault/connector/VaultConnector.java
@@ -21,6 +21,7 @@ import de.stklcode.jvault.connector.exception.VaultConnectorException;
import de.stklcode.jvault.connector.model.*;
import de.stklcode.jvault.connector.model.response.*;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -33,7 +34,7 @@ import java.util.Map;
* @author Stefan Kalscheuer
* @since 0.1
*/
-public interface VaultConnector extends AutoCloseable {
+public interface VaultConnector extends AutoCloseable, Serializable {
/**
* Default sub-path for Vault secrets.
*/
diff --git a/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java b/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java
index ec1b8f0..7d80c83 100644
--- a/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java
+++ b/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java
@@ -22,8 +22,6 @@ import de.stklcode.jvault.connector.exception.TlsException;
import de.stklcode.jvault.connector.exception.VaultConnectorException;
import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
@@ -31,7 +29,6 @@ import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@@ -58,7 +55,7 @@ public final class HTTPVaultConnectorFactory extends VaultConnectorFactory {
private Integer port;
private boolean tls;
private String prefix;
- private SSLContext sslContext;
+ private X509Certificate trustedCA;
private int numberOfRetries;
private Integer timeout;
private String token;
@@ -146,8 +143,23 @@ public final class HTTPVaultConnectorFactory extends VaultConnectorFactory {
* @since 0.4.0
*/
public HTTPVaultConnectorFactory withTrustedCA(final Path cert) throws VaultConnectorException {
- if (cert != null)
- return withSslContext(createSslContext(cert));
+ if (cert != null) {
+ return withTrustedCA(certificateFromFile(cert));
+ } else {
+ this.trustedCA = null;
+ }
+ return this;
+ }
+
+ /**
+ * Add a trusted CA certifiate for HTTPS connections.
+ *
+ * @param cert path to certificate file
+ * @return self
+ * @since 0.8.0
+ */
+ public HTTPVaultConnectorFactory withTrustedCA(final X509Certificate cert) {
+ this.trustedCA = cert;
return this;
}
@@ -158,10 +170,10 @@ public final class HTTPVaultConnectorFactory extends VaultConnectorFactory {
* @param sslContext the SSL context
* @return self
* @since 0.4.0
+ * @deprecated As of 0.8.0 this is no longer supported, please use {@link #withTrustedCA(Path)} or {@link #withTrustedCA(X509Certificate)}.
*/
public HTTPVaultConnectorFactory withSslContext(final SSLContext sslContext) {
- this.sslContext = sslContext;
- return this;
+ throw new UnsupportedOperationException("Use of deprecated method, please switch to withTrustedCA()");
}
/**
@@ -241,59 +253,18 @@ public final class HTTPVaultConnectorFactory extends VaultConnectorFactory {
@Override
public HTTPVaultConnector build() {
- return new HTTPVaultConnector(host, tls, port, prefix, sslContext, numberOfRetries, timeout);
+ return new HTTPVaultConnector(host, tls, port, prefix, trustedCA, numberOfRetries, timeout);
}
@Override
public HTTPVaultConnector buildAndAuth() throws VaultConnectorException {
if (token == null)
throw new ConnectionException("No vault token provided, unable to authenticate.");
- HTTPVaultConnector con = new HTTPVaultConnector(host, tls, port, prefix, sslContext, numberOfRetries, timeout);
+ HTTPVaultConnector con = new HTTPVaultConnector(host, tls, port, prefix, trustedCA, numberOfRetries, timeout);
con.authToken(token);
return con;
}
- /**
- * Create SSL Context trusting only provided certificate.
- *
- * @param trustedCert Path to trusted CA certificate
- * @return SSL context
- * @throws TlsException on errors
- * @since 0.4.0
- */
- private SSLContext createSslContext(final Path trustedCert) throws TlsException {
- try {
- SSLContext context = SSLContext.getInstance("TLS");
- context.init(null, createTrustManager(trustedCert), new SecureRandom());
- return context;
- } catch (NoSuchAlgorithmException | KeyManagementException e) {
- throw new TlsException("Unable to intialize SSLContext", e);
- }
- }
-
- /**
- * Create a custom TrustManager for given CA certificate file.
- *
- * @param trustedCert Path to trusted CA certificate
- * @return TrustManger
- * @throws TlsException on error
- * @since 0.4.0
- */
- private TrustManager[] createTrustManager(final Path trustedCert) throws TlsException {
- try {
- /* Create Keystore with trusted certificate */
- KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
- keyStore.load(null, null);
- keyStore.setCertificateEntry("trustedCert", certificateFromFile(trustedCert));
- /* Initialize TrustManager */
- TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- tmf.init(keyStore);
- return tmf.getTrustManagers();
- } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException e) {
- throw new TlsException("Unable to initialize TrustManager", e);
- }
- }
-
/**
* Read given certificate file to X.509 certificate.
*
diff --git a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java
index 1d09826..1b1ffcc 100644
--- a/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java
+++ b/src/test/java/de/stklcode/jvault/connector/HTTPVaultConnectorOfflineTest.java
@@ -34,10 +34,12 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import javax.net.ssl.SSLContext;
import java.io.IOException;
+import java.io.InputStream;
import java.lang.reflect.Field;
-import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
import java.util.Collections;
import static net.bytebuddy.implementation.MethodDelegation.to;
@@ -89,7 +91,7 @@ public class HTTPVaultConnectorOfflineTest {
.load(HttpClientBuilder.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
// Ignore SSL context settings.
- when(httpMockBuilder.setSSLContext(null)).thenReturn(httpMockBuilder);
+ when(httpMockBuilder.setSSLSocketFactory(any())).thenReturn(httpMockBuilder);
// Re-initialize HTTP mock to ensure fresh (empty) results.
httpMock = mock(CloseableHttpClient.class);
@@ -159,7 +161,7 @@ public class HTTPVaultConnectorOfflineTest {
* Test constductors of the {@link HTTPVaultConnector} class.
*/
@Test
- public void constructorTest() throws NoSuchAlgorithmException {
+ public void constructorTest() throws IOException, CertificateException {
final String url = "https://vault.example.net/test/";
final String hostname = "vault.example.com";
final Integer port = 1337;
@@ -168,7 +170,11 @@ public class HTTPVaultConnectorOfflineTest {
final String expectedNoTls = "http://" + hostname + "/v1/";
final String expectedCustomPort = "https://" + hostname + ":" + port + "/v1/";
final String expectedCustomPrefix = "https://" + hostname + ":" + port + prefix;
- final SSLContext sslContext = SSLContext.getInstance("TLS");
+ X509Certificate trustedCaCert = null;
+
+ try (InputStream is = getClass().getResourceAsStream("/tls/ca.pem")) {
+ trustedCaCert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
+ }
// Most basic constructor expects complete URL.
HTTPVaultConnector connector = new HTTPVaultConnector(url);
@@ -185,15 +191,15 @@ public class HTTPVaultConnectorOfflineTest {
// Specify custom prefix.
connector = new HTTPVaultConnector(hostname, true, port, prefix);
assertThat("Unexpected base URL with custom prefix", getPrivate(connector, "baseURL"), is(expectedCustomPrefix));
- assertThat("SSL context set, but not specified", getPrivate(connector, "sslContext"), is(nullValue()));
+ assertThat("Trusted CA cert set, but not specified", getPrivate(connector, "trustedCaCert"), is(nullValue()));
// Provide custom SSL context.
- connector = new HTTPVaultConnector(hostname, true, port, prefix, sslContext);
+ connector = new HTTPVaultConnector(hostname, true, port, prefix, trustedCaCert);
assertThat("Unexpected base URL with custom prefix", getPrivate(connector, "baseURL"), is(expectedCustomPrefix));
- assertThat("SSL context not filled correctly", getPrivate(connector, "sslContext"), is(sslContext));
+ assertThat("Trusted CA cert not filled correctly", getPrivate(connector, "trustedCaCert"), is(trustedCaCert));
// Specify number of retries.
- connector = new HTTPVaultConnector(url, sslContext, retries);
+ connector = new HTTPVaultConnector(url, trustedCaCert, retries);
assertThat("Number of retries not set correctly", getPrivate(connector, "retries"), is(retries));
}
@@ -466,7 +472,7 @@ public class HTTPVaultConnectorOfflineTest {
private void setPrivate(Object target, String fieldName, Object value) {
try {
Field field = target.getClass().getDeclaredField(fieldName);
- boolean accessible =field.isAccessible();
+ boolean accessible = field.isAccessible();
field.setAccessible(true);
field.set(target, value);
field.setAccessible(accessible);
diff --git a/src/test/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactoryTest.java b/src/test/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactoryTest.java
index 6e067fb..8e4901b 100644
--- a/src/test/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactoryTest.java
+++ b/src/test/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactoryTest.java
@@ -69,7 +69,7 @@ public class HTTPVaultConnectorFactoryTest {
connector = factory.build();
assertThat("URL nor set correctly", getPrivate(connector, "baseURL"), is(equalTo(VAULT_ADDR + "/v1/")));
- assertThat("SSL context set when no cert provided", getPrivate(connector, "sslContext"), is(nullValue()));
+ assertThat("Trusted CA cert set when no cert provided", getPrivate(connector, "trustedCaCert"), is(nullValue()));
assertThat("Non-default number of retries, when none set", getPrivate(connector, "retries"), is(0));
/* Provide address and number of retries */
@@ -83,7 +83,7 @@ public class HTTPVaultConnectorFactoryTest {
connector = factory.build();
assertThat("URL nor set correctly", getPrivate(connector, "baseURL"), is(equalTo(VAULT_ADDR + "/v1/")));
- assertThat("SSL context set when no cert provided", getPrivate(connector, "sslContext"), is(nullValue()));
+ assertThat("Trusted CA cert set when no cert provided", getPrivate(connector, "trustedCaCert"), is(nullValue()));
assertThat("Number of retries not set correctly", getPrivate(connector, "retries"), is(VAULT_MAX_RETRIES));
/* Provide CA certificate */