Skip to content

Commit 0934edf

Browse files
CatiaCorreiawilkinsona
authored andcommitted
Add LDAPS support to embedded LDAP server
Signed-off-by: CatiaCorreia <[email protected]> See gh-48315
1 parent a1ed3bf commit 0934edf

File tree

4 files changed

+380
-5
lines changed

4 files changed

+380
-5
lines changed

module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapAutoConfiguration.java

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,28 @@
1616

1717
package org.springframework.boot.ldap.autoconfigure.embedded;
1818

19+
import java.io.IOException;
1920
import java.io.InputStream;
21+
import java.security.KeyManagementException;
22+
import java.security.KeyStore;
23+
import java.security.KeyStoreException;
24+
import java.security.NoSuchAlgorithmException;
25+
import java.security.SecureRandom;
26+
import java.security.UnrecoverableKeyException;
27+
import java.security.cert.CertificateException;
2028
import java.util.Collections;
2129
import java.util.HashMap;
2230
import java.util.List;
2331
import java.util.Map;
2432

33+
import javax.net.ssl.KeyManager;
34+
import javax.net.ssl.KeyManagerFactory;
35+
import javax.net.ssl.SSLContext;
36+
import javax.net.ssl.SSLServerSocketFactory;
37+
import javax.net.ssl.SSLSocketFactory;
38+
import javax.net.ssl.TrustManager;
39+
import javax.net.ssl.TrustManagerFactory;
40+
2541
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
2642
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
2743
import com.unboundid.ldap.listener.InMemoryListenerConfig;
@@ -33,6 +49,7 @@
3349
import org.springframework.aot.hint.RuntimeHints;
3450
import org.springframework.aot.hint.RuntimeHintsRegistrar;
3551
import org.springframework.beans.factory.DisposableBean;
52+
import org.springframework.beans.factory.ObjectProvider;
3653
import org.springframework.boot.autoconfigure.AutoConfiguration;
3754
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3855
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
@@ -47,6 +64,8 @@
4764
import org.springframework.boot.ldap.autoconfigure.LdapAutoConfiguration;
4865
import org.springframework.boot.ldap.autoconfigure.LdapProperties;
4966
import org.springframework.boot.ldap.autoconfigure.embedded.EmbeddedLdapAutoConfiguration.EmbeddedLdapAutoConfigurationRuntimeHints;
67+
import org.springframework.boot.ssl.SslBundle;
68+
import org.springframework.boot.ssl.SslBundles;
5069
import org.springframework.context.ApplicationContext;
5170
import org.springframework.context.ConfigurableApplicationContext;
5271
import org.springframework.context.annotation.Bean;
@@ -60,9 +79,12 @@
6079
import org.springframework.core.env.MutablePropertySources;
6180
import org.springframework.core.env.PropertySource;
6281
import org.springframework.core.io.Resource;
82+
import org.springframework.core.io.ResourceLoader;
83+
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
6384
import org.springframework.core.type.AnnotatedTypeMetadata;
6485
import org.springframework.ldap.core.ContextSource;
6586
import org.springframework.ldap.core.support.LdapContextSource;
87+
import org.springframework.util.Assert;
6688
import org.springframework.util.StringUtils;
6789

6890
/**
@@ -84,14 +106,18 @@ public final class EmbeddedLdapAutoConfiguration implements DisposableBean {
84106

85107
private final EmbeddedLdapProperties embeddedProperties;
86108

109+
private final ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver();
110+
87111
private @Nullable InMemoryDirectoryServer server;
88112

89113
EmbeddedLdapAutoConfiguration(EmbeddedLdapProperties embeddedProperties) {
90114
this.embeddedProperties = embeddedProperties;
91115
}
92116

93117
@Bean
94-
InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext) throws LDAPException {
118+
InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext,
119+
ObjectProvider<SslBundles> sslBundles) throws LDAPException, KeyStoreException, IOException,
120+
NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException, KeyManagementException {
95121
String[] baseDn = StringUtils.toStringArray(this.embeddedProperties.getBaseDn());
96122
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(baseDn);
97123
String username = this.embeddedProperties.getCredential().getUsername();
@@ -100,9 +126,18 @@ InMemoryDirectoryServer directoryServer(ApplicationContext applicationContext) t
100126
config.addAdditionalBindCredentials(username, password);
101127
}
102128
setSchema(config);
103-
InMemoryListenerConfig listenerConfig = InMemoryListenerConfig.createLDAPConfig("LDAP",
104-
this.embeddedProperties.getPort());
105-
config.setListenerConfigs(listenerConfig);
129+
if (this.embeddedProperties.getSsl().isEnabled()) {
130+
EmbeddedLdapProperties.Ssl ssl = this.embeddedProperties.getSsl();
131+
SSLContext sslContext = getSslContext(ssl, sslBundles.getIfAvailable());
132+
SSLServerSocketFactory serverSocketFactory = sslContext.getServerSocketFactory();
133+
SSLSocketFactory clientSocketFactory = sslContext.getSocketFactory();
134+
config.setListenerConfigs(InMemoryListenerConfig.createLDAPSConfig("LDAPS", null,
135+
this.embeddedProperties.getPort(), serverSocketFactory, clientSocketFactory));
136+
}
137+
else {
138+
config
139+
.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", this.embeddedProperties.getPort()));
140+
}
106141
this.server = new InMemoryDirectoryServer(config);
107142
importLdif(this.server, applicationContext);
108143
this.server.startListening();
@@ -181,6 +216,70 @@ public void destroy() throws Exception {
181216
}
182217
}
183218

219+
private SSLContext getSslContext(EmbeddedLdapProperties.Ssl ssl, @Nullable SslBundles sslBundles)
220+
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
221+
UnrecoverableKeyException, KeyManagementException {
222+
if (sslBundles != null && StringUtils.hasText(ssl.getBundle())) {
223+
SslBundle sslBundle = sslBundles.getBundle(ssl.getBundle());
224+
Assert.notNull(sslBundle, "SSL bundle name has been set but no SSL bundles found in context");
225+
return sslBundle.createSslContext();
226+
227+
}
228+
else {
229+
SSLContext sslContext = SSLContext.getInstance(ssl.getAlgorithm());
230+
KeyManager[] keyManagers = configureKeyManagers(ssl);
231+
TrustManager[] trustManagers = configureTrustManagers(ssl);
232+
sslContext.init(keyManagers, trustManagers, new SecureRandom());
233+
return sslContext;
234+
}
235+
}
236+
237+
private KeyManager @Nullable [] configureKeyManagers(EmbeddedLdapProperties.Ssl ssl) throws KeyStoreException,
238+
IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {
239+
String keyStoreName = ssl.getKeyStore();
240+
String keyStorePassword = ssl.getKeyStorePassword();
241+
String storeType = ssl.getKeyStoreType();
242+
char[] keyPassphrase = null;
243+
if (keyStorePassword != null) {
244+
keyPassphrase = keyStorePassword.toCharArray();
245+
}
246+
KeyManager[] keyManagers = null;
247+
if (StringUtils.hasText(keyStoreName)) {
248+
Resource resource = this.resourceLoader.getResource(keyStoreName);
249+
KeyStore ks = KeyStore.getInstance(storeType);
250+
try (InputStream inputStream = resource.getInputStream()) {
251+
ks.load(inputStream, keyPassphrase);
252+
}
253+
KeyManagerFactory kmf = KeyManagerFactory.getInstance(ssl.getKeyStoreAlgorithm());
254+
kmf.init(ks, keyPassphrase);
255+
keyManagers = kmf.getKeyManagers();
256+
}
257+
return keyManagers;
258+
}
259+
260+
private TrustManager @Nullable [] configureTrustManagers(EmbeddedLdapProperties.Ssl ssl)
261+
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
262+
String trustStoreName = ssl.getTrustStore();
263+
String trustStorePassword = ssl.getTrustStorePassword();
264+
String storeType = ssl.getTrustStoreType();
265+
char[] trustPassphrase = null;
266+
if (trustStorePassword != null) {
267+
trustPassphrase = trustStorePassword.toCharArray();
268+
}
269+
TrustManager[] trustManagers = null;
270+
if (StringUtils.hasText(trustStoreName)) {
271+
Resource resource = this.resourceLoader.getResource(trustStoreName);
272+
KeyStore tks = KeyStore.getInstance(storeType);
273+
try (InputStream inputStream = resource.getInputStream()) {
274+
tks.load(inputStream, trustPassphrase);
275+
}
276+
TrustManagerFactory tmf = TrustManagerFactory.getInstance(ssl.getTrustStoreAlgorithm());
277+
tmf.init(tks);
278+
trustManagers = tmf.getTrustManagers();
279+
}
280+
return trustManagers;
281+
}
282+
184283
/**
185284
* {@link SpringBootCondition} to determine when to apply embedded LDAP
186285
* auto-configuration.

module/spring-boot-ldap/src/main/java/org/springframework/boot/ldap/autoconfigure/embedded/EmbeddedLdapProperties.java

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616

1717
package org.springframework.boot.ldap.autoconfigure.embedded;
1818

19+
import java.security.NoSuchAlgorithmException;
1920
import java.util.ArrayList;
2021
import java.util.List;
2122

23+
import javax.net.ssl.SSLContext;
24+
2225
import org.jspecify.annotations.Nullable;
2326

2427
import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -62,6 +65,11 @@ public class EmbeddedLdapProperties {
6265
*/
6366
private final Validation validation = new Validation();
6467

68+
/**
69+
* SSL configuration.
70+
*/
71+
private final Ssl ssl = new Ssl();
72+
6573
public int getPort() {
6674
return this.port;
6775
}
@@ -98,6 +106,10 @@ public Validation getValidation() {
98106
return this.validation;
99107
}
100108

109+
public Ssl getSsl() {
110+
return this.ssl;
111+
}
112+
101113
public static class Credential {
102114

103115
/**
@@ -132,6 +144,174 @@ boolean isAvailable() {
132144

133145
}
134146

147+
public static class Ssl {
148+
149+
private static final String SUN_X509 = "SunX509";
150+
151+
private static final String DEFAULT_PROTOCOL;
152+
153+
static {
154+
String protocol = "TLSv1.1";
155+
try {
156+
String[] protocols = SSLContext.getDefault().getSupportedSSLParameters().getProtocols();
157+
for (String prot : protocols) {
158+
if ("TLSv1.2".equals(prot)) {
159+
protocol = "TLSv1.2";
160+
break;
161+
}
162+
}
163+
}
164+
catch (NoSuchAlgorithmException ex) {
165+
// nothing
166+
}
167+
DEFAULT_PROTOCOL = protocol;
168+
}
169+
170+
/**
171+
* Whether to enable SSL support.
172+
*/
173+
private Boolean enabled = false;
174+
175+
/**
176+
* SSL bundle name.
177+
*/
178+
private @Nullable String bundle;
179+
180+
/**
181+
* Path to the key store that holds the SSL certificate.
182+
*/
183+
private @Nullable String keyStore;
184+
185+
/**
186+
* Key store type.
187+
*/
188+
private String keyStoreType = "PKCS12";
189+
190+
/**
191+
* Password used to access the key store.
192+
*/
193+
private @Nullable String keyStorePassword;
194+
195+
/**
196+
* Key store algorithm.
197+
*/
198+
private String keyStoreAlgorithm = SUN_X509;
199+
200+
/**
201+
* Trust store that holds SSL certificates.
202+
*/
203+
private @Nullable String trustStore;
204+
205+
/**
206+
* Trust store type.
207+
*/
208+
private String trustStoreType = "JKS";
209+
210+
/**
211+
* Password used to access the trust store.
212+
*/
213+
private @Nullable String trustStorePassword;
214+
215+
/**
216+
* Trust store algorithm.
217+
*/
218+
private String trustStoreAlgorithm = SUN_X509;
219+
220+
/**
221+
* SSL algorithm to use.
222+
*/
223+
private String algorithm = DEFAULT_PROTOCOL;
224+
225+
public Boolean isEnabled() {
226+
return this.enabled;
227+
}
228+
229+
public void setEnabled(Boolean enabled) {
230+
this.enabled = enabled;
231+
}
232+
233+
public @Nullable String getBundle() {
234+
return this.bundle;
235+
}
236+
237+
public void setBundle(@Nullable String bundle) {
238+
this.bundle = bundle;
239+
}
240+
241+
public @Nullable String getKeyStore() {
242+
return this.keyStore;
243+
}
244+
245+
public void setKeyStore(@Nullable String keyStore) {
246+
this.keyStore = keyStore;
247+
}
248+
249+
public String getKeyStoreType() {
250+
return this.keyStoreType;
251+
}
252+
253+
public void setKeyStoreType(String keyStoreType) {
254+
this.keyStoreType = keyStoreType;
255+
}
256+
257+
public @Nullable String getKeyStorePassword() {
258+
return this.keyStorePassword;
259+
}
260+
261+
public void setKeyStorePassword(@Nullable String keyStorePassword) {
262+
this.keyStorePassword = keyStorePassword;
263+
}
264+
265+
public String getKeyStoreAlgorithm() {
266+
return this.keyStoreAlgorithm;
267+
}
268+
269+
public void setKeyStoreAlgorithm(String keyStoreAlgorithm) {
270+
this.keyStoreAlgorithm = keyStoreAlgorithm;
271+
}
272+
273+
public @Nullable String getTrustStore() {
274+
return this.trustStore;
275+
}
276+
277+
public void setTrustStore(@Nullable String trustStore) {
278+
this.trustStore = trustStore;
279+
}
280+
281+
public String getTrustStoreType() {
282+
return this.trustStoreType;
283+
}
284+
285+
public void setTrustStoreType(String trustStoreType) {
286+
this.trustStoreType = trustStoreType;
287+
}
288+
289+
public @Nullable String getTrustStorePassword() {
290+
return this.trustStorePassword;
291+
}
292+
293+
public void setTrustStorePassword(@Nullable String trustStorePassword) {
294+
this.trustStorePassword = trustStorePassword;
295+
}
296+
297+
public String getTrustStoreAlgorithm() {
298+
return this.trustStoreAlgorithm;
299+
}
300+
301+
public void setTrustStoreAlgorithm(String trustStoreAlgorithm) {
302+
this.trustStoreAlgorithm = trustStoreAlgorithm;
303+
}
304+
305+
public String getAlgorithm() {
306+
return this.algorithm;
307+
}
308+
309+
public void setAlgorithm(String sslAlgorithm) {
310+
this.algorithm = sslAlgorithm;
311+
}
312+
313+
}
314+
135315
public static class Validation {
136316

137317
/**

0 commit comments

Comments
 (0)