From 4ac121fdaa00f683ced537257d4dcaf922cc4ede Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 1 Apr 2015 16:30:30 -0600 Subject: [PATCH] Enable configuration of LDAPS and allow SSL verification to be skipped https://www.pivotaltracker.com/story/show/89437874 [#89437874] Set ldap.ssl.skipverification default to false --- .../ldap/LdapIdentityProviderDefinition.java | 19 ++- .../uaa/ldap/ProcessLdapProperties.java | 55 ++++++++ .../LdapIdentityProviderDefinitionTest.java | 11 +- .../uaa/ldap/ProcessLdapPropertiesTest.java | 38 ++++++ uaa/src/main/resources/ldap-integration.xml | 12 +- uaa/src/main/resources/uaa.yml | 5 +- .../DynamicLdapAuthenticationManagerTest.java | 3 +- ...micZoneAwareAuthenticationManagerTest.java | 3 +- .../uaa/mock/ldap/LdapMockMvcTests.java | 106 ++++++++++++--- .../ldap/server/ApacheDsSSLContainer.java | 121 ++++++++++++++++++ 10 files changed, 345 insertions(+), 28 deletions(-) create mode 100644 common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapProperties.java create mode 100644 common/src/test/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapPropertiesTest.java create mode 100644 uaa/src/test/java/org/springframework/security/ldap/server/ApacheDsSSLContainer.java diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinition.java b/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinition.java index 8491967c4ef..896d5b26d9c 100644 --- a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinition.java +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinition.java @@ -37,6 +37,7 @@ public class LdapIdentityProviderDefinition { private boolean autoAddGroups; private boolean groupSearchSubTree; private int maxGroupSearchDepth; + private boolean skipSSLVerification; public static LdapIdentityProviderDefinition searchAndBindMapGroupToScopes( String baseUrl, @@ -51,7 +52,8 @@ public static LdapIdentityProviderDefinition searchAndBindMapGroupToScopes( boolean mailSubstituteOverridesLdap, boolean autoAddGroups, boolean groupSearchSubTree, - int groupMaxSearchDepth) { + int groupMaxSearchDepth, + boolean skipSSLVerification) { LdapIdentityProviderDefinition definition = new LdapIdentityProviderDefinition(); definition.baseUrl = baseUrl; @@ -69,12 +71,16 @@ public static LdapIdentityProviderDefinition searchAndBindMapGroupToScopes( definition.autoAddGroups = autoAddGroups; definition.groupSearchSubTree = groupSearchSubTree; definition.maxGroupSearchDepth = groupMaxSearchDepth; + definition.skipSSLVerification = skipSSLVerification; return definition; } @JsonIgnore public ConfigurableEnvironment getLdapConfigurationEnvironment() { Map properties = new HashMap<>(); + + properties.put("ldap.ssl.skipverification", isSkipSSLVerification()); + if ("ldap/ldap-search-and-bind.xml".equals(ldapProfileFile)) { properties.put("ldap.profile.file", getLdapProfileFile()); properties.put("ldap.base.url", getBaseUrl()); @@ -219,6 +225,14 @@ public void setMaxGroupSearchDepth(int maxGroupSearchDepth) { this.maxGroupSearchDepth = maxGroupSearchDepth; } + public boolean isSkipSSLVerification() { + return skipSSLVerification; + } + + public void setSkipSSLVerification(boolean skipSSLVerification) { + this.skipSSLVerification = skipSSLVerification; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -248,6 +262,8 @@ public boolean equals(Object o) { return false; if (maxGroupSearchDepth!=that.maxGroupSearchDepth) return false; + if (skipSSLVerification!=that.skipSSLVerification) + return false; return true; } @@ -268,6 +284,7 @@ public int hashCode() { result = 31 * result + (mailSubstituteOverridesLdap ? 1 : 0); result = 31 * result + (autoAddGroups ? 1 : 0); result = 31 * result + (groupSearchSubTree ? 1 : 0); + result = 31 * result + (skipSSLVerification ? 1 : 0); result = 31 * result + maxGroupSearchDepth; return result; } diff --git a/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapProperties.java b/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapProperties.java new file mode 100644 index 00000000000..c3f23916f5e --- /dev/null +++ b/common/src/main/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapProperties.java @@ -0,0 +1,55 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.ldap; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class ProcessLdapProperties { + + public static final String LDAP_SOCKET_FACTORY = "java.naming.ldap.factory.socket"; + public static final String SKIP_SSL_VERIFICATION_SOCKET_FACTORY = "org.apache.directory.shared.ldap.util.DummySSLSocketFactory"; + + private boolean disableSslVerification; + private String baseUrl; + + public ProcessLdapProperties(String baseUrl, boolean disableSslVerification) { + this.baseUrl = baseUrl; + this.disableSslVerification = disableSslVerification; + } + + public Map process(Map map) { + Map result = new LinkedHashMap(map); + if (isDisableSslVerification() && isLdapsUrl()) { + result.put(LDAP_SOCKET_FACTORY, SKIP_SSL_VERIFICATION_SOCKET_FACTORY); + } + return result; + } + + public boolean isLdapsUrl() { + return baseUrl!=null && baseUrl.startsWith("ldaps"); + } + public boolean isDisableSslVerification() { + return disableSslVerification; + } + + public void setDisableSslVerification(boolean disableSslVerification) { + this.disableSslVerification = disableSslVerification; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } +} diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinitionTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinitionTest.java index e09ea64587a..3d56c7abf7e 100644 --- a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinitionTest.java +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/LdapIdentityProviderDefinitionTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; public class LdapIdentityProviderDefinitionTest { @@ -46,7 +47,8 @@ public void testSearchAndBindConfiguration() throws Exception { false, true, true, - 100); + 100, + true); String config = JsonUtils.writeValueAsString(ldapIdentityProviderDefinition); LdapIdentityProviderDefinition deserialized = JsonUtils.readValue(config, LdapIdentityProviderDefinition.class); @@ -79,6 +81,10 @@ public void testSearchAndBindConfiguration() throws Exception { assertNotNull(environment.getProperty("ldap.groups.maxSearchDepth")); assertEquals("100", environment.getProperty("ldap.groups.maxSearchDepth")); + //skip ssl verification + assertNotNull(environment.getProperty("ldap.ssl.skipverification")); + assertEquals("true", environment.getProperty("ldap.ssl.skipverification")); + ldapIdentityProviderDefinition = LdapIdentityProviderDefinition.searchAndBindMapGroupToScopes( "ldap://localhost:389/", "cn=admin,ou=Users,dc=test,dc=com", @@ -92,7 +98,8 @@ public void testSearchAndBindConfiguration() throws Exception { true, true, true, - 100); + 100, + true); config = JsonUtils.writeValueAsString(ldapIdentityProviderDefinition); LdapIdentityProviderDefinition deserialized2 = JsonUtils.readValue(config, LdapIdentityProviderDefinition.class); diff --git a/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapPropertiesTest.java b/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapPropertiesTest.java new file mode 100644 index 00000000000..39b153ba8f0 --- /dev/null +++ b/common/src/test/java/org/cloudfoundry/identity/uaa/ldap/ProcessLdapPropertiesTest.java @@ -0,0 +1,38 @@ +/* + * ***************************************************************************** + * Cloud Foundry + * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved. + * This product is licensed to you under the Apache License, Version 2.0 (the "License"). + * You may not use this product except in compliance with the License. + * + * This product includes a number of subcomponents with + * separate copyright notices and license terms. Your use of these + * subcomponents is subject to the terms and conditions of the + * subcomponent's license, as noted in the LICENSE file. + * ***************************************************************************** + */ + +package org.cloudfoundry.identity.uaa.ldap; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.cloudfoundry.identity.uaa.ldap.ProcessLdapProperties.LDAP_SOCKET_FACTORY; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class ProcessLdapPropertiesTest { + + @Test + public void testProcess() throws Exception { + Map properties = new HashMap<>(); + ProcessLdapProperties process = new ProcessLdapProperties("ldap://localhost:389", false); + assertNull(process.process(properties).get(LDAP_SOCKET_FACTORY)); + process.setDisableSslVerification(true); + assertNull(process.process(properties).get(LDAP_SOCKET_FACTORY)); + process.setBaseUrl("ldaps://localhost:636"); + assertEquals(ProcessLdapProperties.SKIP_SSL_VERIFICATION_SOCKET_FACTORY, process.process(properties).get(LDAP_SOCKET_FACTORY)); + } +} \ No newline at end of file diff --git a/uaa/src/main/resources/ldap-integration.xml b/uaa/src/main/resources/ldap-integration.xml index db7d0f2a500..06a961fdb04 100644 --- a/uaa/src/main/resources/ldap-integration.xml +++ b/uaa/src/main/resources/ldap-integration.xml @@ -26,10 +26,20 @@ - + + + + + + + + + + + diff --git a/uaa/src/main/resources/uaa.yml b/uaa/src/main/resources/uaa.yml index a792740b863..fdc835f09cf 100755 --- a/uaa/src/main/resources/uaa.yml +++ b/uaa/src/main/resources/uaa.yml @@ -1,5 +1,5 @@ # Configuration in this file is overridden by an external file -# if any of these exist: +# if any of these exist: # [$UAA_CONFIG_URL, $UAA_CONFIG_PATH/uaa.yml, $CLOUDFOUNDRY_CONFIG_PATH/uaa.yml] #spring_profiles: mysql,default @@ -58,6 +58,7 @@ # url: 'ldaps://192.168.3.39:10636/' # userDnPattern: 'cn={0},ou=Users,dc=test,dc=com;cn={0},ou=OtherUsers,dc=example,dc=com' # ssl: +# skipverification: false # sslCertificate: ! '-----BEGIN CERTIFICATE----- # MIIBfTCCAScCBgFDfaC2yzANBgkqhkiG9w0BAQUFADBCMQswCQYDVQQGEwJVUzEMMAoGA1UEChMD # QVNGMRIwEAYDVQQLEwlEaXJlY3RvcnkxETAPBgNVBAMTCEFwYWNoZURTMB4XDTE0MDExMDE5Mjg0 @@ -86,7 +87,7 @@ # url: 'ldap://localhost:10389/' # userDn: 'cn=admin,dc=test,dc=com' # password: 'password' -# searchBase: '' +# searchBase: '' # searchFilter: 'cn={0}' # passwordAttributeName: userPassword # passwordEncoder: org.cloudfoundry.identity.uaa.login.ldap.DynamicPasswordComparator diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManagerTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManagerTest.java index 232d3895145..b0608a4e58d 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManagerTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicLdapAuthenticationManagerTest.java @@ -28,7 +28,8 @@ public class DynamicLdapAuthenticationManagerTest { false, true, true, - 100); + 100, + true); @Test public void testGetLdapAuthenticationManager() throws Exception { diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java index aef24c20d29..7fb9e496ccd 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/DynamicZoneAwareAuthenticationManagerTest.java @@ -40,7 +40,8 @@ public class DynamicZoneAwareAuthenticationManagerTest { false, true, true, - 100); + 100, + true); AuthenticationManager authzAuthenticationMgr = mock(AuthenticationManager.class); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java index a12c045a41c..d0504c13001 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/ldap/LdapMockMvcTests.java @@ -19,6 +19,7 @@ import org.cloudfoundry.identity.uaa.authentication.manager.ChainedAuthenticationManager; import org.cloudfoundry.identity.uaa.ldap.ExtendedLdapUserMapper; import org.cloudfoundry.identity.uaa.ldap.LdapIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.ldap.ProcessLdapProperties; import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils; import org.cloudfoundry.identity.uaa.rest.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.rest.jdbc.LimitSqlAdapter; @@ -58,6 +59,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.ldap.server.ApacheDSContainer; +import org.springframework.security.ldap.server.ApacheDsSSLContainer; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.security.web.FilterChainProxy; import org.springframework.test.web.servlet.MockMvc; @@ -96,22 +98,25 @@ public class LdapMockMvcTests extends TestClassNullifier { private MockEnvironment mockEnvironment; - @Parameters(name = "{index}: auth[{0}]; group[{1}]") + @Parameters(name = "{index}: auth[{0}]; group[{1}]; url[{2}]") public static Collection data() { return Arrays.asList(new Object[][]{ - {"ldap-simple-bind.xml", "ldap-groups-null.xml"}, - {"ldap-simple-bind.xml", "ldap-groups-as-scopes.xml"}, - {"ldap-simple-bind.xml", "ldap-groups-map-to-scopes.xml"}, - {"ldap-search-and-bind.xml", "ldap-groups-null.xml"}, - {"ldap-search-and-bind.xml", "ldap-groups-as-scopes.xml"}, - {"ldap-search-and-bind.xml", "ldap-groups-map-to-scopes.xml"}, - {"ldap-search-and-compare.xml", "ldap-groups-null.xml"}, - {"ldap-search-and-compare.xml", "ldap-groups-as-scopes.xml"}, - {"ldap-search-and-compare.xml", "ldap-groups-map-to-scopes.xml"} + {"ldap-simple-bind.xml", "ldap-groups-null.xml", "ldap://localhost:33389"}, + {"ldap-simple-bind.xml", "ldap-groups-as-scopes.xml", "ldap://localhost:33389"}, + {"ldap-simple-bind.xml", "ldap-groups-map-to-scopes.xml", "ldap://localhost:33389"}, + {"ldap-simple-bind.xml", "ldap-groups-map-to-scopes.xml", "ldaps://localhost:33636"}, + {"ldap-search-and-bind.xml", "ldap-groups-null.xml", "ldap://localhost:33389"}, + {"ldap-search-and-bind.xml", "ldap-groups-as-scopes.xml", "ldap://localhost:33389"}, + {"ldap-search-and-bind.xml", "ldap-groups-map-to-scopes.xml", "ldap://localhost:33389"}, + {"ldap-search-and-bind.xml", "ldap-groups-map-to-scopes.xml", "ldaps://localhost:33636"}, + {"ldap-search-and-compare.xml", "ldap-groups-null.xml", "ldap://localhost:33389"}, + {"ldap-search-and-compare.xml", "ldap-groups-as-scopes.xml", "ldap://localhost:33389"}, + {"ldap-search-and-compare.xml", "ldap-groups-map-to-scopes.xml", "ldap://localhost:33389"}, + {"ldap-search-and-compare.xml", "ldap-groups-map-to-scopes.xml", "ldaps://localhost:33636"} }); } - private static ApacheDSContainer apacheDS; + private static ApacheDsSSLContainer apacheDS; private static File tmpDir; @AfterClass @@ -125,9 +130,10 @@ public static void startApacheDS() throws Exception { tmpDir.deleteOnExit(); System.out.println(tmpDir); //configure properties for running against ApacheDS - apacheDS = new ApacheDSContainer("dc=test,dc=com","classpath:ldap_init.ldif"); + apacheDS = new ApacheDsSSLContainer("dc=test,dc=com","classpath:ldap_init.ldif"); apacheDS.setWorkingDirectory(tmpDir); apacheDS.setPort(33389); + apacheDS.setSslPort(33636); apacheDS.afterPropertiesSet(); apacheDS.start(); } @@ -143,10 +149,12 @@ public static void startApacheDS() throws Exception { private String ldapProfile; private String ldapGroup; + private String ldapBaseUrl; - public LdapMockMvcTests(String ldapProfile, String ldapGroup) { + public LdapMockMvcTests(String ldapProfile, String ldapGroup, String baseUrl) { this.ldapGroup = ldapGroup; this.ldapProfile = ldapProfile; + this.ldapBaseUrl = baseUrl; } @Before @@ -160,9 +168,10 @@ public void setUp() throws Exception { mockEnvironment.setProperty("ldap.profile.file", "ldap/" + ldapProfile); mockEnvironment.setProperty("ldap.groups.file", "ldap/" + ldapGroup); mockEnvironment.setProperty("ldap.group.maxSearchDepth", "10"); - mockEnvironment.setProperty("ldap.base.url","ldap://localhost:33389"); + mockEnvironment.setProperty("ldap.base.url",ldapBaseUrl); mockEnvironment.setProperty("ldap.base.userDn","cn=admin,ou=Users,dc=test,dc=com"); mockEnvironment.setProperty("ldap.base.password","adminsecret"); + mockEnvironment.setProperty("ldap.ssl.skipverification","true"); webApplicationContext = new XmlWebApplicationContext(); webApplicationContext.setEnvironment(mockEnvironment); @@ -228,7 +237,8 @@ public void testLdapConfigurationBeforeSave() throws Exception { false, true, true, - 10 + 10, + true ); IdentityProvider provider = new IdentityProvider(); @@ -303,7 +313,8 @@ public void testLdapConfigurationBeforeSave() throws Exception { false, true, true, - 10 + 10, + true ); provider.setConfig(JsonUtils.writeValueAsString(definition)); request = new IdentityProviderValidationRequest(provider, token); @@ -334,7 +345,8 @@ public void testLdapConfigurationBeforeSave() throws Exception { false, true, true, - 10 + 10, + true ); provider.setConfig(JsonUtils.writeValueAsString(definition)); request = new IdentityProviderValidationRequest(provider, token); @@ -365,7 +377,8 @@ public void testLdapConfigurationBeforeSave() throws Exception { false, true, true, - 10 + 10, + true ); provider.setConfig(JsonUtils.writeValueAsString(definition)); request = new IdentityProviderValidationRequest(provider, token); @@ -381,6 +394,57 @@ public void testLdapConfigurationBeforeSave() throws Exception { .andExpect(status().isBadRequest()) .andReturn(); assertThat(result.getResponse().getContentAsString(), containsString("Caused by:")); + + ProcessLdapProperties processLdapProperties = webApplicationContext.getBean(ProcessLdapProperties.class); + if (processLdapProperties.isLdapsUrl()) { + token = new UsernamePasswordAuthentication("marissa2", "ldap"); + + //SSL self signed cert problems + definition = LdapIdentityProviderDefinition.searchAndBindMapGroupToScopes( + "ldaps://localhost:33636", + "cn=admin,ou=Users,dc=test,dc=com", + "adminsecret", + "dc=test,dc=com", + "cn={0}", + "ou=scopes,dc=test,dc=com", + "member={0}", + "mail", + null, + false, + true, + true, + 10, + false + ); + provider.setConfig(JsonUtils.writeValueAsString(definition)); + request = new IdentityProviderValidationRequest(provider, token); + post = post("/identity-providers/test") + .header("Accept", APPLICATION_JSON_VALUE) + .header("Content-Type", APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + zoneAdminToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(request)) + .header(IdentityZoneSwitchingFilter.HEADER, zone.getId()); + result = mockMvc.perform(post) + .andExpect(status().isBadRequest()) + .andReturn(); + assertThat(result.getResponse().getContentAsString(), containsString("Caused by:")); + definition.setSkipSSLVerification(true); + provider.setConfig(JsonUtils.writeValueAsString(definition)); + request = new IdentityProviderValidationRequest(provider, token); + post = post("/identity-providers/test") + .header("Accept", APPLICATION_JSON_VALUE) + .header("Content-Type", APPLICATION_JSON_VALUE) + .header("Authorization", "Bearer " + zoneAdminToken) + .contentType(APPLICATION_JSON) + .content(JsonUtils.writeValueAsString(request)) + .header(IdentityZoneSwitchingFilter.HEADER, zone.getId()); + + result = mockMvc.perform(post) + .andExpect(status().isOk()) + .andReturn(); + assertThat(result.getResponse().getContentAsString(), containsString("\"ok\"")); + } } @Test @@ -421,7 +485,8 @@ public void testLoginInNonDefaultZone() throws Exception { false, true, true, - 10 + 10, + true ); IdentityProvider provider = new IdentityProvider(); @@ -471,7 +536,8 @@ public void testLoginInNonDefaultZone() throws Exception { true, true, true, - 10 + 10, + true ); provider.setConfig(JsonUtils.writeValueAsString(definition)); MockMvcUtils.utils().createIdpUsingWebRequest(mockMvc, zone.getId(), zoneAdminToken, provider, status().isOk(), true); diff --git a/uaa/src/test/java/org/springframework/security/ldap/server/ApacheDsSSLContainer.java b/uaa/src/test/java/org/springframework/security/ldap/server/ApacheDsSSLContainer.java new file mode 100644 index 00000000000..eb96ad32f54 --- /dev/null +++ b/uaa/src/test/java/org/springframework/security/ldap/server/ApacheDsSSLContainer.java @@ -0,0 +1,121 @@ +package org.springframework.security.ldap.server; + + +import org.apache.directory.server.ldap.LdapServer; +import org.apache.directory.server.ldap.handlers.extended.StartTlsHandler; +import org.apache.directory.server.protocol.shared.transport.TcpTransport; +import sun.security.x509.CertAndKeyGen; +import sun.security.x509.X500Name; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Date; + +public class ApacheDsSSLContainer extends ApacheDSContainer { + private int port = 53389; + private int sslPort = 53636; + + private String keystoreFile; + private File workingDir; + private boolean useStartTLS = false; + + public boolean isUseStartTLS() { + return useStartTLS; + } + + public void setUseStartTLS(boolean useStartTLS) { + this.useStartTLS = useStartTLS; + } + + public String getKeystoreFile() { + return keystoreFile; + } + + public void setKeystoreFile(String keystoreFile) { + this.keystoreFile = keystoreFile; + } + + public ApacheDsSSLContainer(String root, String ldifs) throws Exception { + super(root, ldifs); + } + + @Override + public void setWorkingDirectory(File workingDir) { + super.setWorkingDirectory(workingDir); + this.workingDir = workingDir; + if (!workingDir.mkdirs()) { + throw new RuntimeException("Unable to create directory:"+workingDir); + } + } + + public File getWorkingDirectory() { + return workingDir; + } + + @Override + public void afterPropertiesSet() throws Exception { + server = new LdapServer(); + server.setDirectoryService(service); + TcpTransport sslTransport = new TcpTransport(sslPort); + if (isUseStartTLS()) { + server.addExtendedOperationHandler(new StartTlsHandler()); + } else { + sslTransport.setEnableSSL(true); + } + TcpTransport tcpTransport = new TcpTransport(port); + server.setTransports(sslTransport, tcpTransport); + assert server.isEnableLdaps(sslTransport); + assert !server.isEnableLdaps(tcpTransport); + server.setCertificatePassword("password"); + server.setKeystoreFile(getKeystore(getWorkingDirectory()).getAbsolutePath()); + start(); + } + + public void setSslPort(int sslPort) { + this.sslPort = sslPort; + } + + @Override + public void setPort(int port) { + super.setPort(port); + this.port = port; + } + + + private static final int keysize = 1024; + private static final String commonName = "localhost"; + private static final String organizationalUnit = "UAA"; + private static final String organization = "Pivotal Software"; + private static final String city = "San Francisco"; + private static final String state = "CA"; + private static final String country = "UA"; + private static final long validity = 1096; // 3 years + private static final String alias = "uaa-ldap"; + private static final char[] keyPass = "password".toCharArray(); + + //mimic what the keytool does + public File getKeystore(File directory) throws Exception { + + KeyStore keyStore = KeyStore.getInstance("JKS"); + keyStore.load(null, null); + CertAndKeyGen keypair = new CertAndKeyGen("RSA", "SHA1WithRSA", null); + X500Name x500Name = new X500Name(commonName, organizationalUnit, organization, city, state, country); + keypair.generate(keysize); + PrivateKey privKey = keypair.getPrivateKey(); + X509Certificate[] chain = new X509Certificate[1]; + chain[0] = keypair.getSelfCertificate(x500Name, new Date(), (long) validity * 24 * 60 * 60); + keyStore.setKeyEntry(alias, privKey, keyPass, chain); + String keystoreName = "ldap.keystore"; + File keystore = new File(directory, keystoreName); + if (!keystore.createNewFile()) { + throw new FileNotFoundException("Unable to create file:"+keystore); + } + keyStore.store(new FileOutputStream(keystore,false), keyPass); + return keystore; + } +} +