Merged HEAD-BUG-FIX (5.0/Cloud) to HEAD (5.0/Cloud)

84138: Merged FEATURE2 (5.0.0.FEATURE2) to HEAD-BUG-FIX (5.0/Cloud)
      83970: ACE-2268 : make ldaps (LDAP over SSL) configurable using ldap subsystem properties and not only JVM (system) properties (JAVA_OPTS)
      Implemented the LDAPS truststore configuration via subsystem's properties.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@84634 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Alan Davis
2014-09-18 17:26:44 +00:00
parent 2b5e64a359
commit 2b15a5b5ad
7 changed files with 382 additions and 5 deletions

View File

@@ -102,6 +102,17 @@
-->
<bean id="ldapInitialDirContextFactory" class="org.alfresco.repo.security.authentication.ldap.LDAPInitialDirContextFactoryImpl">
<property name="trustStorePath">
<value>${ldap.authentication.truststore.path}</value>
</property>
<property name="trustStoreType">
<value>${ldap.authentication.truststore.type}</value>
</property>
<property name="trustStorePassPhrase">
<value>${ldap.authentication.truststore.passphrase}</value>
</property>
<property name="initialDirContextEnvironment">
<map>
<!-- The LDAP provider -->
@@ -133,6 +144,11 @@
<entry key="java.naming.referral">
<value>follow</value>
</entry>
<!-- Set to 'ssl' to enable LDAPS configuration via subsystem's properties -->
<entry key="java.naming.security.protocol">
<value>${ldap.authentication.java.naming.security.protocol}</value>
</entry>
</map>
</property>
<property name="defaultIntialDirContextEnvironment">
@@ -187,6 +203,11 @@
<entry key="java.naming.referral">
<value>follow</value>
</entry>
<!-- Set to 'ssl' to enable LDAPS configuration via subsystem's properties -->
<entry key="java.naming.security.protocol">
<value>${ldap.authentication.java.naming.security.protocol}</value>
</entry>
</map>
</property>
</bean>

View File

@@ -124,3 +124,10 @@ ldap.synchronization.enableProgressEstimation=true
# Requests timeout, in miliseconds, use 0 for none (default)
ldap.authentication.java.naming.read.timeout=0
# LDAPS truststore configuration properties
#ldap.authentication.truststore.path=
#ldap.authentication.truststore.passphrase=
#ldap.authentication.truststore.type=
# Set to 'ssl' to enable truststore configuration via subsystem's properties
#ldap.authentication.java.naming.security.protocol=ssl

View File

@@ -130,3 +130,10 @@ ldap.synchronization.enableProgressEstimation=true
# Requests timeout, in miliseconds, use 0 for none (default)
ldap.authentication.java.naming.read.timeout=0
# LDAPS truststore configuration properties
#ldap.authentication.truststore.path=
#ldap.authentication.truststore.passphrase=
#ldap.authentication.truststore.type=
# Set to 'ssl' to enable truststore configuration via subsystem's properties
#ldap.authentication.java.naming.security.protocol=ssl

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.security.authentication;
import org.alfresco.error.AlfrescoRuntimeException;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
* SSL socket factory that uses custom trustStore
* <br>The factory should be first initialized
*
* @author alex.mukha
* @since 5.0
*/
public class AlfrescoSSLSocketFactory extends SSLSocketFactory
{
private static SSLContext context;
public AlfrescoSSLSocketFactory()
{
}
/**
* Initialize the factory with custom trustStore
* @param trustStore
*/
public static synchronized void initTrustedSSLSocketFactory(final KeyStore trustStore)
{
try
{
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(trustStore);
context = SSLContext.getInstance("SSL");
context.init(null, trustManagerFactory.getTrustManagers(), SecureRandom.getInstance("SHA1PRNG"));
}
catch (NoSuchAlgorithmException nsae)
{
throw new AlfrescoRuntimeException("The SSL socket factory cannot be initialized.", nsae);
}
catch (KeyStoreException kse)
{
throw new AlfrescoRuntimeException("The SSL socket factory cannot be initialized.", kse);
}
catch (KeyManagementException kme)
{
throw new AlfrescoRuntimeException("The SSL socket factory cannot be initialized.", kme);
}
}
public static synchronized SocketFactory getDefault()
{
if (context == null)
{
throw new AlfrescoRuntimeException("The factory was not initialized.");
}
return new AlfrescoSSLSocketFactory();
}
@Override
public String[] getDefaultCipherSuites()
{
return context.getSocketFactory().getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites()
{
return context.getSocketFactory().getSupportedCipherSuites();
}
@Override
public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException
{
return context.getSocketFactory().createSocket(socket, s, i, b);
}
@Override
public Socket createSocket(String s, int i) throws IOException, UnknownHostException
{
return context.getSocketFactory().createSocket(s, i);
}
@Override
public Socket createSocket(String s, int i, InetAddress inetAddress, int i2) throws IOException, UnknownHostException
{
return context.getSocketFactory().createSocket(s, i, inetAddress, i2);
}
@Override
public Socket createSocket(InetAddress inetAddress, int i) throws IOException
{
return context.getSocketFactory().createSocket(inetAddress, i);
}
@Override
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress2, int i2) throws IOException
{
return context.getSocketFactory().createSocket(inetAddress, i, inetAddress2, i2);
}
}

View File

@@ -18,7 +18,13 @@
*/
package org.alfresco.repo.security.authentication.ldap;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
@@ -41,9 +47,12 @@ import javax.naming.ldap.LdapContext;
import javax.naming.ldap.PagedResultsControl;
import javax.naming.ldap.PagedResultsResponseControl;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.AlfrescoSSLSocketFactory;
import org.alfresco.repo.security.authentication.AuthenticationDiagnostic;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.util.ApplicationContextHelper;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
@@ -58,6 +67,48 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
private Map<String, String> defaultEnvironment = Collections.<String, String> emptyMap();
private Map<String, String> authenticatedEnvironment = Collections.<String, String> emptyMap();
private String trustStorePath;
private String trustStoreType;
private String trustStorePassPhrase;
public String getTrustStorePath()
{
return trustStorePath;
}
public void setTrustStorePath(String trustStorePath)
{
if (PropertyCheck.isValidPropertyString(trustStorePath))
{
this.trustStorePath = trustStorePath;
}
}
public String getTrustStoreType()
{
return trustStoreType;
}
public void setTrustStoreType(String trustStoreType)
{
if (PropertyCheck.isValidPropertyString(trustStoreType))
{
this.trustStoreType = trustStoreType;
}
}
public String getTrustStorePassPhrase()
{
return trustStorePassPhrase;
}
public void setTrustStorePassPhrase(String trustStorePassPhrase)
{
if (PropertyCheck.isValidPropertyString(trustStorePassPhrase))
{
this.trustStorePassPhrase = trustStorePassPhrase;
}
}
static
{
@@ -114,6 +165,13 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
String securityPrincipal = env.get(Context.SECURITY_PRINCIPAL);
String providerURL = env.get(Context.PROVIDER_URL);
if (isSSLSocketFactoryRequired())
{
KeyStore trustStore = initTrustStore();
AlfrescoSSLSocketFactory.initTrustedSSLSocketFactory(trustStore);
env.put("java.naming.ldap.factory.socket", AlfrescoSSLSocketFactory.class.getName());
}
if(diagnostic == null)
{
diagnostic = new AuthenticationDiagnostic();
@@ -392,6 +450,12 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
env.putAll(authenticatedEnvironment);
env.remove(Context.SECURITY_PRINCIPAL);
env.remove(Context.SECURITY_CREDENTIALS);
if (isSSLSocketFactoryRequired())
{
KeyStore trustStore = initTrustStore();
AlfrescoSSLSocketFactory.initTrustedSSLSocketFactory(trustStore);
env.put("java.naming.ldap.factory.socket", AlfrescoSSLSocketFactory.class.getName());
}
try
{
new InitialDirContext(env);
@@ -418,6 +482,12 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
env.putAll(authenticatedEnvironment);
env.put(Context.SECURITY_PRINCIPAL, "daftAsABrush");
env.put(Context.SECURITY_CREDENTIALS, "daftAsABrush");
if (isSSLSocketFactoryRequired())
{
KeyStore trustStore = initTrustStore();
AlfrescoSSLSocketFactory.initTrustedSSLSocketFactory(trustStore);
env.put("java.naming.ldap.factory.socket", AlfrescoSSLSocketFactory.class.getName());
}
try
{
@@ -447,6 +517,12 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
env.putAll(authenticatedEnvironment);
env.put(Context.SECURITY_PRINCIPAL, "cn=daftAsABrush,dc=woof");
env.put(Context.SECURITY_CREDENTIALS, "daftAsABrush");
if (isSSLSocketFactoryRequired())
{
KeyStore trustStore = initTrustStore();
AlfrescoSSLSocketFactory.initTrustedSSLSocketFactory(trustStore);
env.put("java.naming.ldap.factory.socket", AlfrescoSSLSocketFactory.class.getName());
}
try
{
@@ -481,6 +557,12 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
env.putAll(authenticatedEnvironment);
env.put(Context.SECURITY_PRINCIPAL, principal);
env.put(Context.SECURITY_CREDENTIALS, "sdasdasdasdasd123123123");
if (isSSLSocketFactoryRequired())
{
KeyStore trustStore = initTrustStore();
AlfrescoSSLSocketFactory.initTrustedSSLSocketFactory(trustStore);
env.put("java.naming.ldap.factory.socket", AlfrescoSSLSocketFactory.class.getName());
}
if (!checkedEnvs.contains(env))
{
@@ -513,7 +595,80 @@ public class LDAPInitialDirContextFactoryImpl implements LDAPInitialDirContextFa
}
}
/**
* Check if it required to use custom SSL socket factory with custom trustStore.
* <br>Required for LDAPS configuration. The <code>ldap.authentication.java.naming.security.protocol</code> should be set to "ssl" for LDAPS.
* <br>The following properties should be set:
* <ul>
* <li>ldap.authentication.truststore.path
* <li>ldap.authentication.truststore.type
* <li>ldap.authentication.truststore.passphrase
* <li>ldap.authentication.java.naming.security.protocol
* </ul>
*
* @return <code>true</code> if all the required properties are set
*/
private boolean isSSLSocketFactoryRequired()
{
boolean result = false;
// Check for LDAPS config
String protocol = authenticatedEnvironment.get(Context.SECURITY_PROTOCOL);
if (protocol != null && protocol.equals("ssl"))
{
if (getTrustStoreType() != null && getTrustStorePath() != null && getTrustStoreType() != null)
{
result = true;
}
else
{
logger.warn("The SSL configuration for LDAPS is not full, the default configuration will be used.");
}
}
return result;
}
/**
* Initialize trustStore with Spring set properties:
* <ul>
* <li>ldap.authentication.truststore.path
* <li>ldap.authentication.truststore.type
* <li>ldap.authentication.truststore.passphrase
* </ul>
*
* @return {@link KeyStore} with loaded trustStore file
*/
private KeyStore initTrustStore()
{
KeyStore ks;
String trustStoreType = getTrustStoreType();
try
{
ks = KeyStore.getInstance(trustStoreType);
}
catch (KeyStoreException kse)
{
throw new AlfrescoRuntimeException("No provider supports " + trustStoreType, kse);
}
try
{
ks.load(new FileInputStream(getTrustStorePath()), getTrustStorePassPhrase().toCharArray());
}
catch (FileNotFoundException fnfe)
{
throw new AlfrescoRuntimeException("The truststore file is not found.", fnfe);
}
catch (IOException ioe)
{
throw new AlfrescoRuntimeException("The truststore file cannot be read.", ioe);
}
catch (NoSuchAlgorithmException nsae)
{
throw new AlfrescoRuntimeException("Algorithm used to check the integrity of the truststore cannot be found.", nsae);
}
catch (CertificateException ce)
{
throw new AlfrescoRuntimeException("The certificates cannot be loaded from truststore.", ce);
}
return ks;
}
}

View File

@@ -23,6 +23,7 @@ import junit.framework.Test;
import junit.framework.TestSuite;
import org.alfresco.repo.ownable.impl.OwnableServiceTest;
import org.alfresco.repo.security.authentication.AlfrescoSSLSocketFactoryTest;
import org.alfresco.repo.security.authentication.AuthenticationBootstrapTest;
import org.alfresco.repo.security.authentication.AuthenticationTest;
import org.alfresco.repo.security.authentication.AuthorizationTest;
@@ -81,7 +82,7 @@ public class SecurityTestSuite extends TestSuite
suite.addTestSuite(AuthorityBridgeTableAsynchronouslyRefreshedCacheTest.class);
suite.addTest(new JUnit4TestAdapter(HomeFolderProviderSynchronizerTest.class));
suite.addTest(new JUnit4TestAdapter(AlfrescoSSLSocketFactoryTest.class));
return suite;
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2005-2014 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.security.authentication;
import org.alfresco.error.AlfrescoRuntimeException;
import org.junit.Test;
import java.security.KeyStore;
import static org.junit.Assert.fail;
/**
* SSL socket factory test
*
* @author alex.mukha
* @since 5.0
*/
public class AlfrescoSSLSocketFactoryTest
{
private static final String KEYSTORE_TYPE = "JCEKS";
@Test
public void testConfiguration() throws Exception
{
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE);
// try to use the factory without initialization
try
{
AlfrescoSSLSocketFactory.getDefault();
fail("An AlfrescoRuntimeException should be thrown as the factory is not initialized.");
}
catch (AlfrescoRuntimeException are)
{
// Expected
}
// initialize and get an instance of AlfrescoSSLSocketFactory
AlfrescoSSLSocketFactory.initTrustedSSLSocketFactory(ks);
AlfrescoSSLSocketFactory.getDefault();
}
}