Introduce char-array factory methods for SslConfiguration and VaultToken.

We now store the VaultToken and SslConfiguration key store/trust store passwords as char-array internally to pass-thru passwords to KeyStoreManagerFactory/TrustManagerFactory. VaultToken stores the token as char[] as well but due to its use (HttpHeaders, HTTP Path usage) it's not possible to replace String usage with char-array usage.

Closes gh-82.
This commit is contained in:
Mark Paluch
2017-04-19 17:54:55 +02:00
parent 0927f67f2f
commit 8fbfdd9e27
7 changed files with 314 additions and 114 deletions

View File

@@ -32,7 +32,7 @@ class LoginToken extends VaultToken {
private final long leaseDuration;
private LoginToken(String token, long leaseDuration, boolean renewable) {
private LoginToken(char[] token, long leaseDuration, boolean renewable) {
super(token);
@@ -47,6 +47,20 @@ class LoginToken extends VaultToken {
* @return the created {@link VaultToken}
*/
public static LoginToken of(String token) {
Assert.hasText(token, "Token must not be empty");
return of(token, 0);
}
/**
* Create a new {@link LoginToken}.
*
* @param token must not be {@literal null}.
* @return the created {@link VaultToken}
* @since 1.1
*/
public static LoginToken of(char[] token) {
return of(token, 0);
}
@@ -61,6 +75,22 @@ class LoginToken extends VaultToken {
Assert.hasText(token, "Token must not be empty");
return of(token.toCharArray(), leaseDuration);
}
/**
* Create a new {@link LoginToken} with a {@code leaseDuration}.
*
* @param token must not be {@literal null}.
* @param leaseDuration the lease duration.
* @return the created {@link VaultToken}
* @since 1.1
*/
public static LoginToken of(char[] token, long leaseDuration) {
Assert.notNull(token, "Token must not be null");
Assert.isTrue(token.length > 0, "Token must not be empty");
return new LoginToken(token, leaseDuration, false);
}
@@ -75,6 +105,22 @@ class LoginToken extends VaultToken {
Assert.hasText(token, "Token must not be empty");
return renewable(token.toCharArray(), leaseDuration);
}
/**
* Create a new renewable {@link LoginToken} with a {@code leaseDuration}.
*
* @param token must not be {@literal null}.
* @param leaseDuration the lease duration.
* @return the created {@link VaultToken}
* @since 1.1
*/
public static LoginToken renewable(char[] token, long leaseDuration) {
Assert.notNull(token, "Token must not be null");
Assert.isTrue(token.length > 0, "Token must not be empty");
return new LoginToken(token, leaseDuration, true);
}

View File

@@ -43,7 +43,6 @@ import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultSchemePortResolver;
import org.apache.http.impl.conn.SystemDefaultRoutePlanner;
import org.springframework.core.io.Resource;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.Netty4ClientHttpRequestFactory;
@@ -55,6 +54,7 @@ import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.vault.support.ClientOptions;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
/**
* Factory for {@link ClientHttpRequestFactory} that supports Apache HTTP Components,
@@ -136,14 +136,11 @@ public class ClientHttpRequestFactoryFactory {
throws GeneralSecurityException, IOException {
KeyManager[] keyManagers = sslConfiguration.getKeyStore() != null ? createKeyManagerFactory(
sslConfiguration.getKeyStore(), sslConfiguration.getKeyStorePassword(),
sslConfiguration.getKeyStoreType()).getKeyManagers()
sslConfiguration.getKeyStoreConfiguration()).getKeyManagers()
: null;
TrustManager[] trustManagers = sslConfiguration.getTrustStore() != null ? createTrustManagerFactory(
sslConfiguration.getTrustStore(),
sslConfiguration.getTrustStorePassword(),
sslConfiguration.getTrustStoreType()).getTrustManagers()
sslConfiguration.getTrustStoreConfiguration()).getTrustManagers()
: null;
SSLContext sslContext = SSLContext.getInstance("TLS");
@@ -152,34 +149,38 @@ public class ClientHttpRequestFactoryFactory {
return sslContext;
}
private static KeyManagerFactory createKeyManagerFactory(Resource keystoreFile,
String storePassword, String storeType) throws GeneralSecurityException,
private static KeyManagerFactory createKeyManagerFactory(
KeyStoreConfiguration keyStoreConfiguration)
throws GeneralSecurityException,
IOException {
KeyStore keyStore = KeyStore
.getInstance(StringUtils.hasText(storeType) ? storeType : KeyStore
.getDefaultType());
.getInstance(StringUtils.hasText(keyStoreConfiguration.getStoreType())
? keyStoreConfiguration.getStoreType()
: KeyStore.getDefaultType());
loadKeyStore(keystoreFile, storePassword, keyStore);
loadKeyStore(keyStoreConfiguration, keyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore,
StringUtils.hasText(storePassword) ? storePassword.toCharArray()
: new char[0]);
keyStoreConfiguration.getStorePassword() == null ? new char[0]
: keyStoreConfiguration.getStorePassword());
return keyManagerFactory;
}
private static TrustManagerFactory createTrustManagerFactory(Resource trustFile,
String storePassword, String storeType) throws GeneralSecurityException,
private static TrustManagerFactory createTrustManagerFactory(
KeyStoreConfiguration keyStoreConfiguration)
throws GeneralSecurityException,
IOException {
KeyStore trustStore = KeyStore
.getInstance(StringUtils.hasText(storeType) ? storeType : KeyStore
.getDefaultType());
.getInstance(StringUtils.hasText(keyStoreConfiguration.getStoreType())
? keyStoreConfiguration.getStoreType()
: KeyStore.getDefaultType());
loadKeyStore(trustFile, storePassword, trustStore);
loadKeyStore(keyStoreConfiguration, trustStore);
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
@@ -188,16 +189,14 @@ public class ClientHttpRequestFactoryFactory {
return trustManagerFactory;
}
private static void loadKeyStore(Resource keyStoreResource, String storePassword,
private static void loadKeyStore(KeyStoreConfiguration keyStoreConfiguration,
KeyStore keyStore) throws IOException, NoSuchAlgorithmException,
CertificateException {
InputStream inputStream = null;
try {
inputStream = keyStoreResource.getInputStream();
keyStore.load(inputStream,
StringUtils.hasText(storePassword) ? storePassword.toCharArray()
: null);
inputStream = keyStoreConfiguration.getResource().getInputStream();
keyStore.load(inputStream, keyStoreConfiguration.getStorePassword());
}
finally {
if (inputStream != null) {
@@ -332,16 +331,12 @@ public class ClientHttpRequestFactoryFactory {
if (sslConfiguration.getTrustStore() != null) {
sslContextBuilder.trustManager(createTrustManagerFactory(
sslConfiguration.getTrustStore(),
sslConfiguration.getTrustStorePassword(),
sslConfiguration.getTrustStoreType()));
sslConfiguration.getTrustStoreConfiguration()));
}
if (sslConfiguration.getKeyStore() != null) {
sslContextBuilder.keyManager(createKeyManagerFactory(
sslConfiguration.getKeyStore(),
sslConfiguration.getKeyStorePassword(),
sslConfiguration.getKeyStoreType()));
sslConfiguration.getKeyStoreConfiguration()));
}
requestFactory.setSslContext(sslContextBuilder.sslProvider(

View File

@@ -31,6 +31,7 @@ import org.springframework.vault.authentication.AppRoleAuthentication;
import org.springframework.vault.authentication.AppRoleAuthenticationOptions;
import org.springframework.vault.authentication.AwsEc2Authentication;
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions;
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions.AwsEc2AuthenticationOptionsBuilder;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.ClientCertificateAuthentication;
import org.springframework.vault.authentication.CubbyholeAuthentication;
@@ -39,7 +40,6 @@ import org.springframework.vault.authentication.IpAddressUserId;
import org.springframework.vault.authentication.MacAddressUserId;
import org.springframework.vault.authentication.StaticUserId;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions.AwsEc2AuthenticationOptionsBuilder;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.VaultToken;
@@ -133,8 +133,8 @@ import org.springframework.web.client.RestOperations;
* @see CubbyholeAuthentication
*/
@Configuration
public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration implements
ApplicationContextAware {
public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration
implements ApplicationContextAware {
private RestOperations cachedRestOperations;
private ApplicationContext applicationContext;
@@ -175,7 +175,8 @@ public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration im
Resource keyStore = getResource("vault.ssl.key-store");
String keyStorePassword = getProperty("vault.ssl.key-store-password");
Resource trustStore = getResource("vault.ssl.trust-store");
String trustStorePassword = getProperty("vault.ssl.trust-store-password");
String trustStorePassword = getEnvironment()
.getProperty("vault.ssl.trust-store-password");
return new SslConfiguration(keyStore, keyStorePassword, trustStore,
trustStorePassword);
@@ -281,8 +282,8 @@ public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration im
Assert.hasText(roleId,
"Vault AWS EC2 authentication: RoleId (vault.aws-ec2.role-id) must not be empty");
AwsEc2AuthenticationOptionsBuilder builder = AwsEc2AuthenticationOptions
.builder().role(roleId);
AwsEc2AuthenticationOptionsBuilder builder = AwsEc2AuthenticationOptions.builder()
.role(roleId);
if (StringUtils.hasText(identityDocument)) {
builder.identityDocumentUri(URI.create(identityDocument));
@@ -294,7 +295,7 @@ public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration im
protected ClientAuthentication cubbyholeAuthentication() {
String token = getProperty("vault.token");
String token = getEnvironment().getProperty("vault.token");
Assert.hasText(token,
"Vault Cubbyhole authentication: Initial token (vault.token) must not be empty");

View File

@@ -16,6 +16,7 @@
package org.springframework.vault.support;
import java.security.KeyStore;
import java.util.Arrays;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
@@ -38,72 +39,70 @@ public class SslConfiguration {
/**
* Default {@link SslConfiguration} without a KeyStore/TrustStore configured.
*/
public static final SslConfiguration NONE = new SslConfiguration(null, null, null,
null);
public static final SslConfiguration NONE = new SslConfiguration(
KeyStoreConfiguration.EMPTY, KeyStoreConfiguration.EMPTY);
/**
* Trust store that holds certificates and private keys.
*/
private final Resource keyStore;
private final KeyStoreConfiguration keyStoreConfiguration;
/**
* Password used to access the key store.
*/
private final String keyStorePassword;
/**
* Keystore type.
*/
private final String keyStoreType;
/**
* Trust store that holds SSL certificates.
*/
private final Resource trustStore;
/**
* Password used to access the trust store.
*/
private final String trustStorePassword;
/**
* Truststore type.
*/
private final String trustStoreType;
private final KeyStoreConfiguration trustStoreConfiguration;
/**
* Create a new {@link SslConfiguration} with the default {@link KeyStore} type.
*
* @param keyStore the keystore resource.
* @param keyStorePassword the keystore password.
* @param trustStore the truststore resource.
* @param trustStorePassword the truststore password.
* @param keyStore the key store resource.
* @param keyStorePassword the key store password.
* @param trustStore the trust store resource.
* @param trustStorePassword the trust store password.
* @deprecated Since 1.1, use
* {@link #SslConfiguration(KeyStoreConfiguration, KeyStoreConfiguration)} to prevent
* {@link String} interning and retaining passwords represented as String longer from
* GC than necessary.
*/
@Deprecated
public SslConfiguration(Resource keyStore, String keyStorePassword,
Resource trustStore, String trustStorePassword) {
this(keyStore, keyStorePassword, KeyStore.getDefaultType(), trustStore,
trustStorePassword, KeyStore.getDefaultType());
this(new KeyStoreConfiguration(keyStore, charsOrNull(keyStorePassword),
KeyStore.getDefaultType()),
new KeyStoreConfiguration(trustStore, charsOrNull(trustStorePassword),
KeyStore.getDefaultType()));
}
/**
* Create a new {@link SslConfiguration}.
*
* @param keyStore the keystore resource.
* @param keyStorePassword the keystore password.
* @param trustStore the truststore resource.
* @param trustStorePassword the truststore password.
* @param keyStoreConfiguration the key store configuration.
* @param trustStoreConfiguration the trust store configuration.
* @since 1.1
*/
public SslConfiguration(Resource keyStore, String keyStorePassword,
String keyStoreType, Resource trustStore, String trustStorePassword,
String trustStoreType) {
public SslConfiguration(KeyStoreConfiguration keyStoreConfiguration,
KeyStoreConfiguration trustStoreConfiguration) {
this.keyStore = keyStore;
this.keyStorePassword = keyStorePassword;
this.keyStoreType = keyStoreType;
this.trustStore = trustStore;
this.trustStorePassword = trustStorePassword;
this.trustStoreType = trustStoreType;
Assert.notNull(keyStoreConfiguration, "KeyStore configuration must not be null");
Assert.notNull(trustStoreConfiguration,
"TrustStore configuration must not be null");
this.keyStoreConfiguration = keyStoreConfiguration;
this.trustStoreConfiguration = trustStoreConfiguration;
}
/**
* Create a new {@link SslConfiguration} for the given trust store with the default
* {@link KeyStore} type.
*
* @param trustStore resource pointing to an existing trust store, must not be
* {@literal null}.
* @param trustStorePassword may be {@literal null}.
* @return the created {@link SslConfiguration}.
* @see java.security.KeyStore
* @deprecated Since 1.1, use {@link #forTrustStore(Resource, char[])} to prevent
* {@link String} interning and retaining passwords represented as String longer from
* GC than necessary.
*/
@Deprecated
public static SslConfiguration forTrustStore(Resource trustStore,
String trustStorePassword) {
return forTrustStore(trustStore, charsOrNull(trustStorePassword));
}
/**
@@ -117,13 +116,34 @@ public class SslConfiguration {
* @see java.security.KeyStore
*/
public static SslConfiguration forTrustStore(Resource trustStore,
String trustStorePassword) {
char[] trustStorePassword) {
Assert.notNull(trustStore, "TrustStore must not be null");
Assert.notNull(trustStore.exists(),
String.format("TrustStore %s does not exist", trustStore));
return new SslConfiguration(null, null, trustStore, trustStorePassword);
return new SslConfiguration(KeyStoreConfiguration.EMPTY,
new KeyStoreConfiguration(trustStore, trustStorePassword,
KeyStore.getDefaultType()));
}
/**
* Create a new {@link SslConfiguration} for the given key store with the default
* {@link KeyStore} type.
*
* @param keyStore resource pointing to an existing key store, must not be
* {@literal null}.
* @param keyStorePassword may be {@literal null}.
* @return the created {@link SslConfiguration}.
* @see java.security.KeyStore
* @deprecated Since 1.1, use {@link #forKeyStore(Resource, char[])} to prevent
* {@link String} interning and retaining passwords represented as String longer from
* GC than necessary.
*/
@Deprecated
public static SslConfiguration forKeyStore(Resource keyStore,
String keyStorePassword) {
return forKeyStore(keyStore, charsOrNull(keyStorePassword));
}
/**
@@ -136,13 +156,38 @@ public class SslConfiguration {
* @return the created {@link SslConfiguration}.
* @see java.security.KeyStore
*/
public static SslConfiguration forKeyStore(Resource keyStore, String keyStorePassword) {
public static SslConfiguration forKeyStore(Resource keyStore,
char[] keyStorePassword) {
Assert.notNull(keyStore, "KeyStore must not be null");
Assert.notNull(keyStore.exists(),
String.format("KeyStore %s does not exist", keyStore));
return new SslConfiguration(keyStore, keyStorePassword, null, null);
return new SslConfiguration(new KeyStoreConfiguration(keyStore, keyStorePassword,
KeyStore.getDefaultType()), KeyStoreConfiguration.EMPTY);
}
/**
* Create a new {@link SslConfiguration} for the given truststore with the default
* {@link KeyStore} type.
*
* @param keyStore resource pointing to an existing keystore, must not be
* {@literal null}.
* @param keyStorePassword may be {@literal null}.
* @param trustStore resource pointing to an existing trust store, must not be
* {@literal null}.
* @param trustStorePassword may be {@literal null}.
* @return the created {@link SslConfiguration}.
* @see java.security.KeyStore
* @deprecated Since 1.1, use {@link #create(Resource, char[], Resource, char[])} to
* prevent {@link String} interning and retaining passwords represented as String
* longer from GC than necessary.
*/
@Deprecated
public SslConfiguration create(Resource keyStore, String keyStorePassword,
Resource trustStore, String trustStorePassword) {
return create(keyStore, charsOrNull(keyStorePassword), trustStore,
charsOrNull(trustStorePassword));
}
/**
@@ -158,8 +203,8 @@ public class SslConfiguration {
* @return the created {@link SslConfiguration}.
* @see java.security.KeyStore
*/
public SslConfiguration create(Resource keyStore, String keyStorePassword,
Resource trustStore, String trustStorePassword) {
public SslConfiguration create(Resource keyStore, char[] keyStorePassword,
Resource trustStore, char[] trustStorePassword) {
Assert.notNull(keyStore, "KeyStore must not be null");
Assert.notNull(keyStore.exists(),
@@ -169,8 +214,11 @@ public class SslConfiguration {
Assert.notNull(trustStore.exists(),
String.format("TrustStore %s does not exist", trustStore));
return new SslConfiguration(keyStore, keyStorePassword, trustStore,
trustStorePassword);
return new SslConfiguration(
new KeyStoreConfiguration(keyStore, keyStorePassword,
KeyStore.getDefaultType()),
new KeyStoreConfiguration(trustStore, trustStorePassword,
KeyStore.getDefaultType()));
}
/**
@@ -178,21 +226,26 @@ public class SslConfiguration {
* not configured.
*/
public Resource getKeyStore() {
return keyStore;
return keyStoreConfiguration.getResource();
}
/**
* @return the key store password or {@literal null} if not configured.
* @deprecated Since 1.1, use {@link KeyStoreConfiguration#getStorePassword()} to
* prevent {@link String} interning and retaining passwords represented as String
* longer from GC than necessary.
*/
@Deprecated
public String getKeyStorePassword() {
return keyStorePassword;
return stringOrNull(keyStoreConfiguration.getStorePassword());
}
/**
* @return the key store type or {@literal null} if not configured.
* @return the key store configuration.
* @since 1.1
*/
public String getKeyStoreType() {
return keyStoreType;
public KeyStoreConfiguration getKeyStoreConfiguration() {
return keyStoreConfiguration;
}
/**
@@ -200,20 +253,99 @@ public class SslConfiguration {
* not configured.
*/
public Resource getTrustStore() {
return trustStore;
return trustStoreConfiguration.getResource();
}
/**
* @return the trust store password or {@literal null} if not configured.
* @deprecated Since 1.1, use {@link KeyStoreConfiguration#getStorePassword()} to
* prevent {@link String} interning and retaining passwords represented as String
* longer from GC than necessary.
*/
@Deprecated
public String getTrustStorePassword() {
return trustStorePassword;
return stringOrNull(trustStoreConfiguration.getStorePassword());
}
/**
* @return the trust store type or {@literal null} if not configured.
* @return the key store configuration.
* @since 1.1
*/
public String getTrustStoreType() {
return trustStoreType;
public KeyStoreConfiguration getTrustStoreConfiguration() {
return trustStoreConfiguration;
}
private static String stringOrNull(char[] storePassword) {
return storePassword != null ? new String(storePassword) : null;
}
private static char[] charsOrNull(String trustStorePassword) {
return trustStorePassword == null ? null : trustStorePassword.toCharArray();
}
/**
* Configuration for a key store/trust store.
*
* @since 1.1
*/
public static class KeyStoreConfiguration {
public final static KeyStoreConfiguration EMPTY = new KeyStoreConfiguration(null,
null, null);
/**
* Store that holds certificates, private keys, ….
*/
private final Resource resource;
/**
* Password used to access the key store/trust store.
*/
private final char[] storePassword;
/**
* Key store/trust store type.
*/
private final String storeType;
/**
* Create a new {@link KeyStoreConfiguration}.
*/
public KeyStoreConfiguration(Resource resource, char[] storePassword,
String storeType) {
this.resource = resource;
this.storeType = storeType;
if (storePassword == null) {
this.storePassword = null;
}
else {
this.storePassword = Arrays.copyOf(storePassword, storePassword.length);
}
}
/**
* @return the {@link java.security.KeyStore key store} resource or
* {@literal null} if not configured.
*/
public Resource getResource() {
return resource;
}
/**
* @return the key store/trust store password or {@literal null} if not
* configured.
*/
public char[] getStorePassword() {
return storePassword;
}
/**
* @return the trust store type or {@literal null} if not configured.
*/
public String getStoreType() {
return storeType;
}
}
}

View File

@@ -16,6 +16,8 @@
package org.springframework.vault.support;
import java.util.Arrays;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@@ -30,13 +32,14 @@ import org.springframework.util.Assert;
@ToString(exclude = "token")
public class VaultToken {
private final String token;
private final char[] token;
protected VaultToken(String token) {
protected VaultToken(char[] token) {
Assert.hasText(token, "Token must not be empty");
Assert.notNull(token, "Token must not be null");
Assert.isTrue(token.length > 0, "Token must not be empty");
this.token = token;
this.token = Arrays.copyOf(token, token.length);
}
/**
@@ -46,6 +49,20 @@ public class VaultToken {
* @return the created {@link VaultToken}
*/
public static VaultToken of(String token) {
Assert.hasText(token, "Token must not be empty");
return of(token.toCharArray());
}
/**
* Create a new {@link VaultToken}.
*
* @param token must not be empty or {@literal null}.
* @return the created {@link VaultToken}
* @since 1.1
*/
public static VaultToken of(char[] token) {
return new VaultToken(token);
}
@@ -53,6 +70,14 @@ public class VaultToken {
* @return the token value.
*/
public String getToken() {
return new String(token);
}
/**
* @return the token value.
* @since 1.1
*/
public char[] toCharArray() {
return token;
}

View File

@@ -120,7 +120,7 @@ public class LifecycleAwareSessionManagerIntegrationTests extends IntegrationTes
try {
restOperations.getForEntity("auth/token/lookup/{token}",
Map.class, loginToken.getToken());
Map.class, loginToken.toCharArray());
fail("Missing HttpStatusCodeException");
}
catch (HttpStatusCodeException e) {

View File

@@ -23,7 +23,7 @@ import org.springframework.vault.support.VaultToken;
/**
* Utility to retrieve settings during test.
*
*
* @author Mark Paluch
*/
public class Settings {
@@ -35,8 +35,8 @@ public class Settings {
File workDir = findWorkDir();
return SslConfiguration.forTrustStore(new FileSystemResource(new File(workDir,
"keystore.jks")), "changeit");
return SslConfiguration.forTrustStore(
new FileSystemResource(new File(workDir, "keystore.jks")), "changeit");
}
/**
@@ -78,7 +78,8 @@ public class Settings {
* @return the token to use during tests.
*/
public static VaultToken token() {
return VaultToken.of(System.getProperty("vault.token",
"00000000-0000-0000-0000-000000000000"));
return VaultToken.of(
System.getProperty("vault.token", "00000000-0000-0000-0000-000000000000")
.toCharArray());
}
}