From 406bb1d4d516472314a2f431ca1365649e8cb523 Mon Sep 17 00:00:00 2001 From: Gunnar Hillert Date: Mon, 24 Oct 2016 20:54:34 -1000 Subject: [PATCH] Add LDAPs support to ApacheDSContainer * Add the ability to enable LDAP over SSL (LDAPs) * Add tests Fixes gh-4096 --- .../ldap/server/ApacheDSContainerTests.java | 106 +++++++++++++++++- .../security/ldap/server/spring.keystore | Bin 0 -> 2234 bytes .../ldap/server/ApacheDSContainer.java | 52 ++++++++- 3 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 ldap/src/integration-test/resources/org/springframework/security/ldap/server/spring.keystore diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java index 2547823cda..ae32eba0be 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/server/ApacheDSContainerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,23 +17,37 @@ package org.springframework.security.ldap.server; import static org.assertj.core.api.Assertions.fail; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.net.ServerSocket; +import java.security.UnrecoverableKeyException; import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.FileCopyUtils; /** * Useful for debugging the container by itself. * * @author Luke Taylor * @author Rob Winch + * @author Gunnar Hillert * @since 3.0 */ public class ApacheDSContainerTests { + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + // SEC-2162 @Test public void failsToStartThrowsException() throws Exception { @@ -95,6 +109,96 @@ public class ApacheDSContainerTests { } } + @Test + public void startWithLdapOverSslWithoutCertificate() throws Exception { + ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", + "classpath:test-server.ldif"); + List ports = getDefaultPorts(1); + server.setPort(ports.get(0)); + server.setLdapOverSslEnabled(true); + + try { + server.afterPropertiesSet(); + } + catch (IllegalArgumentException e){ + assertEquals("When LdapOverSsl is enabled, the keyStoreFile property must be set.", e.getMessage()); + return; + } + fail("Expected an IllegalArgumentException to be thrown."); + } + + @Test + public void startWithLdapOverSslWithWrongPassword() throws Exception { + final ClassPathResource keyStoreResource = new ClassPathResource("/org/springframework/security/ldap/server/spring.keystore"); + final File temporaryKeyStoreFile = new File(temporaryFolder.getRoot(), "spring.keystore"); + FileCopyUtils.copy(keyStoreResource.getInputStream(), new FileOutputStream(temporaryKeyStoreFile)); + + assertTrue(temporaryKeyStoreFile.isFile()); + + ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", + "classpath:test-server.ldif"); + + List ports = getDefaultPorts(1); + server.setPort(ports.get(0)); + + server.setLdapOverSslEnabled(true); + server.setKeyStoreFile(temporaryKeyStoreFile); + server.setCertificatePassord("incorrect-password"); + + try { + server.afterPropertiesSet(); + } + catch (RuntimeException e){ + assertEquals("Server startup failed", e.getMessage()); + assertTrue("Expected an instance of 'UnrecoverableKeyException' but got " + ExceptionUtils.getRootCause(e).getClass().getName(), ExceptionUtils.getRootCause(e) instanceof UnrecoverableKeyException); + return; + } + fail("Expected a RuntimeException to be thrown."); + } + + /** + * This test starts an LDAP server using LDAPs (LDAP over SSL). A self-signed certificate is being used, which was + * previously generated with: + * + *
+	 * {@code
+	 * keytool -genkey -alias spring -keyalg RSA -keystore spring.keystore -validity 3650 -storetype JKS \
+	 * -dname "CN=localhost, OU=Spring, O=Pivotal, L=Kailua-Kona, ST=HI, C=US" -keypass spring -storepass spring
+	 * }
+	 * 
+ * @throws Exception + */ + @Test + public void startWithLdapOverSsl() throws Exception { + + final ClassPathResource keyStoreResource = new ClassPathResource("/org/springframework/security/ldap/server/spring.keystore"); + final File temporaryKeyStoreFile = new File(temporaryFolder.getRoot(), "spring.keystore"); + FileCopyUtils.copy(keyStoreResource.getInputStream(), new FileOutputStream(temporaryKeyStoreFile)); + + assertTrue(temporaryKeyStoreFile.isFile()); + + ApacheDSContainer server = new ApacheDSContainer("dc=springframework,dc=org", + "classpath:test-server.ldif"); + + List ports = getDefaultPorts(1); + server.setPort(ports.get(0)); + + server.setLdapOverSslEnabled(true); + server.setKeyStoreFile(temporaryKeyStoreFile); + server.setCertificatePassord("spring"); + + try { + server.afterPropertiesSet(); + } + finally { + try { + server.destroy(); + } + catch (Throwable t) { + } + } + } + private List getDefaultPorts(int count) throws IOException { List connections = new ArrayList(); List availablePorts = new ArrayList(count); diff --git a/ldap/src/integration-test/resources/org/springframework/security/ldap/server/spring.keystore b/ldap/src/integration-test/resources/org/springframework/security/ldap/server/spring.keystore new file mode 100644 index 0000000000000000000000000000000000000000..c696785da3a5e68aca85fbe834bb51c5e6ae07ff GIT binary patch literal 2234 zcmcgt=Tp-Q7EMA3B?M4P5MDxARuL?}P$GyTDumvGv`CTOLJggO6hTGloo4|9@=%I^ z(whd5A_NhG0f9UakQxL-Q()2Aoq2!3emEb_xifd}oO|co{gwR{5C{Z01o$^#B>zB9 zU-v_5wZq_NfIyrOAP%|@@WXf&U|=X%9&!W><^)0Gpo{u;KIzMgz2?zd9nou9b7v=K zMZXcfuD;&)f9b`STStq)LEt}yJj*DtWuBbztt1~)-Qx>}C z;jr*f*rz1LThyQcsD#$UOx=!_RqLz6&k7SGw_T zQl3!iS`6b-MNO;f?jSB3)=3#k9V>7vKN#{~)1~?Iso{!B)Mfn=iUZt&nN$3yc1^CI z4l}a;SlXbpCLlV@iTCtu)3I|q{&YuOGt0ipg}uFIX3o1=_oN*$7k|`s8@2E)r){W0 zL}^N~2Q>O6`T_xKOEYc&yTmGWI$A6`DJE;>Q=Y3aJE{;QgRwfFI4N8N?`J+W|Ia?D z6ODVggB1#0VsbB9;r94DZr9mpl1n4hlCZq*9A>b!E;*evU|e6E{E#|&S|@6L-1KoI zS+qTBP{W#oi(vp*Jg~3v1`NtSo_`T#H#XS6B}VpYDW7)0zgiMWlNfaSFk|e|d9boo zME7|=uP2WbYC4dH_diCiw=7=iF(d77#SWM@;mg#~S8v%#$8XEUCJ4~{>o+2%xx_*( zj~!$ZKU6J2g4=~ae)8w0vh9PYN2pv?k(TvniSBcCU7d;=Y=7k?qshzeh z>upYS218-^^3gxn+>g@?*2^=m)s*Hi*Nm_CJi4%q7gKiGP)ozrZJtt|>eI7Hjjhy4 zy+)3l?A@!hVF(SKw2WF$u~?jTSkK_w7&7afzKMmGTVmxD1>Z6wjb07YYogJw zY?B@DJnzqH3>0mbj_}L&9R}_=KPq*iyyMPKzMh^`P`#+| z#~R?h4iZa(=7csT+jsB%NPSb)a&wJ^cV^-f$_a&iEDnBw5qKta_Gcaoedl(f+)h+e zTZKMaMbw+`M62M(IX$Jm;IxgqUk?^(DFH9TN%vN++ztjCr=TOOvbm6K(rnD@n*pz6 zOPc3Un-}FAv<1+EY$^`^AT5}Oi3`-W=|-zTNEvw5MV+^75h=~z$;fGaMg>pA&>2(8g)X8- z*GLMciK*J^jZV-M5^m|vydF*CE|AMUSzKvA5cDvwpVF0U+_%4eH`}X06D4CvjUR#! z9j6RjyHVxJd87DI(I3SMARCrms8w4`i)L8gwn(GKH^!t~W+ThUhw30}Xg1W!sb4?a zq0`AosjwFtHFo}UtdRX=Rw)#j_^G33_>Be}SLUlKn}(t0TE4gmp%NaBN!ffE8|Jy6 zqTs3r;Q)ic6lp*bKpid-#SayQ3Mc10`->~SOaUW`6kkHNQz~|vTmNa|;UyiK2nJO~ z`L)h8DOsQT*T~(t^Wyj~>=R5uVx@2qI*_`8{Nedt*S;8?@1&ROzDb5?$@*rr9RaPj zEN^XJOe&kCct}c~j1+O|AeH_xgSYa+v$TQL9oI^oOETK+{Rr0c$(Yb7+_?$Ts*`)5 zinR1snvtka?N&f8@nAW}X)Dp3wYN!6Iy5{T77T6b};?I5$iabSUO8x(Mn27 zL(o{R--u^b-f_;8(6cism45y!{{GE9XtQgGK3-YJuo)^cOp__&smbLzp>6EyacYyZ R*Ck7)FNyDOYO;A3?mxCc=+^)M literal 0 HcmV?d00001 diff --git a/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java b/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java index a0008ec4c2..57a682ea57 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java +++ b/ldap/src/main/java/org/springframework/security/ldap/server/ApacheDSContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,11 +62,12 @@ import org.springframework.util.Assert; * application context is closed to allow the bean to be disposed of and the server * shutdown prior to attempting to start it again. *

- * This class is intended for testing and internal security namespace use and is not - * considered part of framework public API. + * This class is intended for testing and internal security namespace use, only, and is not + * considered part of the framework's public API. * * @author Luke Taylor * @author Rob Winch + * @author Gunnar Hillert */ public class ApacheDSContainer implements InitializingBean, DisposableBean, Lifecycle, ApplicationContextAware { @@ -84,6 +85,10 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life private final String root; private int port = 53389; + private boolean ldapOverSslEnabled; + private File keyStoreFile; + private String certificatePassord; + public ApacheDSContainer(String root, String ldifs) throws Exception { this.ldifResources = ldifs; service = new DefaultDirectoryService(); @@ -126,11 +131,21 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life setWorkingDirectory(new File(apacheWorkDir)); } + if (this.ldapOverSslEnabled && this.keyStoreFile == null) { + throw new IllegalArgumentException("When LdapOverSsl is enabled, the keyStoreFile property must be set."); + } server = new LdapServer(); server.setDirectoryService(service); // AbstractLdapIntegrationTests assume IPv4, so we specify the same here - server.setTransports(new TcpTransport(port)); + + TcpTransport transport = new TcpTransport(port); + if (ldapOverSslEnabled) { + transport.setEnableSSL(true); + server.setKeystoreFile(this.keyStoreFile.getAbsolutePath()); + server.setCertificatePassword(this.certificatePassord); + } + server.setTransports(transport); start(); } @@ -167,6 +182,35 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life this.port = port; } + /** + * If set to {@code true} will enable LDAP over SSL (LDAPs). If set to {@code true} + * {@link ApacheDSContainer#setCertificatePassord(String)} must be set as well. + * + * @param ldapOverSslEnabled If not set, will default to false + */ + public void setLdapOverSslEnabled(boolean ldapOverSslEnabled) { + this.ldapOverSslEnabled = ldapOverSslEnabled; + } + + /** + * The keyStore must not be null and must be a valid file. Will set the keyStore file on the underlying {@link LdapServer}. + * @param keyStoreFile Mandatory if LDAPs is enabled + */ + public void setKeyStoreFile(File keyStoreFile) { + Assert.notNull(keyStoreFile, "The keyStoreFile must not be null."); + Assert.isTrue(keyStoreFile.isFile(), "The keyStoreFile must be a file."); + this.keyStoreFile = keyStoreFile; + } + + /** + * Will set the certificate password on the underlying {@link LdapServer}. + * + * @param certificatePassord May be null + */ + public void setCertificatePassord(String certificatePassord) { + this.certificatePassord = certificatePassord; + } + public DefaultDirectoryService getService() { return service; }