Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public class JksSslBundleProperties extends SslBundleProperties {
*/
private final Store truststore = new Store();

/**
* Key properties.
*/
private final JksKey key = new JksKey();

public Store getKeystore() {
return this.keystore;
}
Expand All @@ -48,6 +53,11 @@ public Store getTruststore() {
return this.truststore;
}

@Override
public JksKey getKey() {
return this.key;
}

/**
* Store properties.
*/
Expand Down Expand Up @@ -107,4 +117,53 @@ public void setPassword(@Nullable String password) {

}

public static class JksKey extends Key {
/**
* The alias that identifies the server key in the key store.
*/
private @Nullable String serverAlias;

/**
* The alias that identifies the client key in the key store.
*/
private @Nullable String clientAlias;

public @Nullable String getServerAlias() {
return this.serverAlias;
}

public void setServerAlias(@Nullable String serverAlias) {
this.serverAlias = serverAlias;
}

public @Nullable String getClientAlias() {
return this.clientAlias;
}

public void setClientAlias(@Nullable String clientAlias) {
this.clientAlias = clientAlias;
}

/**
* Alias that identifies the key in the key store. Deprecated in favor of {@link #getServerAlias()}
* @return the server key alias
* @deprecated since 4.1.0 for removal in 4.3.0 in favor of {@link #getServerAlias()}
*/
@Override
@Deprecated(since = "4.1.0", forRemoval = true)
public @Nullable String getAlias() {
return super.getAlias();
}

/**
* Alias that identifies the key in the key store. Deprecated in favor of {@link #setServerAlias(String)}
* @param alias the server key alias to set
* @deprecated since 4.1.0 for removal in 4.3.0 in favor of {@link #setServerAlias(String)}
*/
@Override
@Deprecated(since = "4.1.0", forRemoval = true)
public void setAlias(@Nullable String alias) {
super.setAlias(alias);
}
}
Comment on lines +120 to +168
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is a custom Key sub-class needed? I expect the changes to be made directly to the existing org.springframework.boot.autoconfigure.ssl.SslBundleProperties.Key class.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done because of the way that JKS and PEM SSL Bundles behave:
JKS based bundles may use a keystore with multiple certificate/key entries (here alias based picking makes sense, otherwise there is no way to pick the proper certificate)

PEM based bundles may only have a single certificate/private-key pair, which means you cannot have this multi certificate/key behavior like you do with JKS bundles, therefore, alias picking doesn't really make sense here in my opinion. (you can of course check if it's the correct alias, but other than that, it's always 1 certificate, therefore that 1 certificate will be used)

of course, if it's still intended to have client/server aliases for PEM aswell, I'll adjust these changes accordingly.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Thanks.

Unfortunately, I think we need to take a few steps back in that case. A JKS-specific feature would, ideally, only affect code in org.springframework.boot.ssl.jks and the more generic code in org.springframework.boot.pem would be unchanged.

I think we need to do some design work and figure out what to do here so I'm going to close this one. Please open an issue for your problem and we can take things from there.

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import org.jspecify.annotations.Nullable;

import org.springframework.boot.autoconfigure.ssl.JksSslBundleProperties.JksKey;
import org.springframework.boot.autoconfigure.ssl.SslBundleProperties.Key;
import org.springframework.boot.io.ApplicationResourceLoader;
import org.springframework.boot.ssl.SslBundle;
Expand Down Expand Up @@ -67,7 +68,15 @@ private PropertiesSslBundle(SslStoreBundle stores, SslBundleProperties propertie
}

private static SslBundleKey asSslKeyReference(@Nullable Key key) {
return (key != null) ? SslBundleKey.of(key.getPassword(), key.getAlias()) : SslBundleKey.NONE;
if (key == null) {
return SslBundleKey.NONE;
}

if (key instanceof JksKey jksKey) {
return SslBundleKey.of(jksKey.getPassword(), jksKey.getServerAlias(), jksKey.getClientAlias());
}

return SslBundleKey.of(key.getPassword(), key.getAlias());
}

private static SslOptions asSslOptions(SslBundleProperties.@Nullable Options options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ void pemPropertiesAreMappedToSslBundle() throws Exception {
@Test
void jksPropertiesAreMappedToSslBundle() {
JksSslBundleProperties properties = new JksSslBundleProperties();
properties.getKey().setAlias("alias");
properties.getKey().setServerAlias("server-alias");
properties.getKey().setClientAlias("client-alias");
properties.getKey().setPassword("secret");
properties.getOptions().setCiphers(Set.of("cipher1", "cipher2", "cipher3"));
properties.getOptions().setEnabledProtocols(Set.of("protocol1", "protocol2"));
Expand All @@ -99,7 +100,9 @@ void jksPropertiesAreMappedToSslBundle() {
properties.getTruststore().setType("PKCS12");
properties.getTruststore().setLocation("classpath:org/springframework/boot/autoconfigure/ssl/keystore.pkcs12");
SslBundle sslBundle = PropertiesSslBundle.get(properties);
assertThat(sslBundle.getKey().getAlias()).isEqualTo("alias");
assertThat(sslBundle.getKey().getAlias()).isEqualTo("server-alias");
assertThat(sslBundle.getKey().getServerAlias()).isEqualTo("server-alias");
assertThat(sslBundle.getKey().getClientAlias()).isEqualTo("client-alias");
assertThat(sslBundle.getKey().getPassword()).isEqualTo("secret");
assertThat(sslBundle.getOptions().getCiphers()).containsExactlyInAnyOrder("cipher1", "cipher2", "cipher3");
assertThat(sslBundle.getOptions().getEnabledProtocols()).containsExactlyInAnyOrder("protocol1", "protocol2");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import javax.net.ssl.SSLEngine;
import javax.net.ssl.X509ExtendedKeyManager;

import org.jspecify.annotations.Nullable;

/**
* {@link KeyManagerFactory} that allows a configurable key alias to be used. Due to the
* fact that the actual calls to retrieve the key by alias are done at request time the
Expand All @@ -45,8 +47,8 @@
*/
final class AliasKeyManagerFactory extends KeyManagerFactory {

AliasKeyManagerFactory(KeyManagerFactory delegate, String alias, String algorithm) {
super(new AliasKeyManagerFactorySpi(delegate, alias), delegate.getProvider(), algorithm);
AliasKeyManagerFactory(KeyManagerFactory delegate, @Nullable String serverAlias, @Nullable String clientAlias, String algorithm) {
super(new AliasKeyManagerFactorySpi(delegate, serverAlias, clientAlias), delegate.getProvider(), algorithm);
}

/**
Expand All @@ -56,11 +58,14 @@ private static final class AliasKeyManagerFactorySpi extends KeyManagerFactorySp

private final KeyManagerFactory delegate;

private final String alias;
private final @Nullable String serverAlias;

private final @Nullable String clientAlias;

private AliasKeyManagerFactorySpi(KeyManagerFactory delegate, String alias) {
private AliasKeyManagerFactorySpi(KeyManagerFactory delegate, @Nullable String serverAlias, @Nullable String clientAlias) {
this.delegate = delegate;
this.alias = alias;
this.serverAlias = serverAlias;
this.clientAlias = clientAlias;
}

@Override
Expand All @@ -85,7 +90,7 @@ protected KeyManager[] engineGetKeyManagers() {
}

private AliasKeyManagerFactory.AliasX509ExtendedKeyManager wrap(X509ExtendedKeyManager keyManager) {
return new AliasX509ExtendedKeyManager(keyManager, this.alias);
return new AliasX509ExtendedKeyManager(keyManager, this.serverAlias, this.clientAlias);
}

}
Expand All @@ -97,30 +102,49 @@ static final class AliasX509ExtendedKeyManager extends X509ExtendedKeyManager {

private final X509ExtendedKeyManager delegate;

private final String alias;
private final @Nullable String serverAlias;

private AliasX509ExtendedKeyManager(X509ExtendedKeyManager keyManager, String alias) {
private final @Nullable String clientAlias;

private AliasX509ExtendedKeyManager(X509ExtendedKeyManager keyManager, @Nullable String serverAlias, @Nullable String clientAlias) {
this.delegate = keyManager;
this.alias = alias;
this.serverAlias = serverAlias;
this.clientAlias = clientAlias;
}

@Override
public String chooseEngineClientAlias(String[] strings, Principal[] principals, SSLEngine sslEngine) {
if (this.clientAlias != null) {
return this.clientAlias;
}

return this.delegate.chooseEngineClientAlias(strings, principals, sslEngine);
}

@Override
public String chooseEngineServerAlias(String s, Principal[] principals, SSLEngine sslEngine) {
return this.alias;
if (this.serverAlias != null) {
return this.serverAlias;
}

return this.delegate.chooseEngineServerAlias(s, principals, sslEngine);
}

@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
if (this.clientAlias != null) {
return this.clientAlias;
}

return this.delegate.chooseClientAlias(keyType, issuers, socket);
}

@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
if (this.serverAlias != null) {
return this.serverAlias;
}

return this.delegate.chooseServerAlias(keyType, issuers, socket);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,21 @@ class DefaultSslManagerBundle implements SslManagerBundle {
public KeyManagerFactory getKeyManagerFactory() {
try {
KeyStore store = this.storeBundle.getKeyStore();
this.key.assertContainsAlias(store);
String alias = this.key.getAlias();
String serverAlias = this.key.getServerAlias();
String clientAlias = this.key.getClientAlias();

this.key.assertContainsAliases(store);

String algorithm = KeyManagerFactory.getDefaultAlgorithm();

KeyManagerFactory factory = getKeyManagerFactoryInstance(algorithm);
factory = (alias != null) ? new AliasKeyManagerFactory(factory, alias, algorithm) : factory;
factory = (serverAlias != null || clientAlias != null) ?
new AliasKeyManagerFactory(factory, serverAlias, clientAlias, algorithm) :
factory;

String password = this.key.getPassword();
password = (password != null) ? password : this.storeBundle.getKeyStorePassword();

factory.init(store, (password != null) ? password.toCharArray() : null);
return factory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,34 @@ public interface SslBundleKey {
*/
@Nullable String getAlias();

/**
* Return the alias of the server key or {@code null} if the server key has no alias.
* @return the server key alias
* @since 4.1.0
*/
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked as @since 4.1.0

default @Nullable String getServerAlias() {
return this.getAlias();
}

/**
* Return the alias of the client key or {@code null} if the client key has no alias.
* @return the client key alias
* @since 4.1.0
*/
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked as @since 4.1.0

default @Nullable String getClientAlias() {
return null;
}

/**
* Assert that the alias is contained in the given keystore.
* @param keyStore the keystore to check
* @deprecated since 4.1.0 for removal in 4.3.0 in favor of {@link #assertContainsAliases(KeyStore)}
*/
@Deprecated(since = "4.1.0", forRemoval = true)
default void assertContainsAlias(@Nullable KeyStore keyStore) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be deprecated for removal in 4.3.0 in favor of a new assertContainsAliases(@Nullable KeyStore keyStore) method. This new method would check that all of the key's aliases are present in the key store.

String alias = getAlias();
if (StringUtils.hasLength(alias) && keyStore != null) {

if (keyStore != null && StringUtils.hasLength(alias)) {
try {
Assert.state(keyStore.containsAlias(alias),
() -> String.format("Keystore does not contain alias '%s'", alias));
Expand All @@ -69,6 +90,37 @@ default void assertContainsAlias(@Nullable KeyStore keyStore) {
}
}

default void assertContainsAliases(@Nullable KeyStore keyStore) {
String serverAlias = getServerAlias();
String clientAlias = getClientAlias();

if (keyStore == null) {
return;
}

try {
if (StringUtils.hasLength(serverAlias)) {
Assert.state(keyStore.containsAlias(serverAlias),
() -> String.format("Keystore does not contain server alias '%s'", serverAlias));
}
}
catch (KeyStoreException ex) {
throw new IllegalStateException(
String.format("Could not determine if keystore contains server alias '%s'", serverAlias), ex);
}

try {
if (StringUtils.hasLength(clientAlias)) {
Assert.state(keyStore.containsAlias(clientAlias),
() -> String.format("Keystore does not contain client alias '%s'", clientAlias));
}
}
catch (KeyStoreException ex) {
throw new IllegalStateException(
String.format("Could not determine if keystore contains client alias '%s'", clientAlias), ex);
}
}

/**
* Factory method to create a new {@link SslBundleKey} instance.
* @param password the password used to access the key
Expand Down Expand Up @@ -108,4 +160,46 @@ public String toString() {
};
}

/**
* Factory method to create a new {@link SslBundleKey} instance.
* @param password the password used to access the key
* @param serverAlias the alias of the server key
* @param clientAlias the alias of the client key
* @return a new {@link SslBundleKey} instance
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be marked as @since 4.1.0.

* @since 4.1.0
*/
static SslBundleKey of(@Nullable String password, @Nullable String serverAlias, @Nullable String clientAlias) {
return new SslBundleKey() {

@Override
public @Nullable String getPassword() {
return password;
}

@Override
public @Nullable String getAlias() {
return serverAlias;
}

@Override
public @Nullable String getServerAlias() {
return serverAlias;
}

@Override
public @Nullable String getClientAlias() {
return clientAlias;
}

@Override
public String toString() {
ToStringCreator creator = new ToStringCreator(this);
creator.append("serverAlias", serverAlias);
creator.append("clientAlias", clientAlias);
creator.append("password", (password != null) ? "******" : null);
return creator.toString();
}

};
}
}
Loading