diff --git a/pom.xml b/pom.xml index 6e1f5c59a7..cc63a43e29 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ none - 2.1.9 + 2.1.10-SNAPSHOT 1.0.0-M8 2.0.2 2.0.0-M3 diff --git a/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/ReplicationTrustManager.java b/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/ReplicationTrustManager.java index 108e07ca7b..2400e53e72 100644 --- a/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/ReplicationTrustManager.java +++ b/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/ReplicationTrustManager.java @@ -22,6 +22,9 @@ import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; import java.security.KeyStore; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -38,9 +41,11 @@ /** * A X509TrustManager implementation used by the replication subsystem. - * This implementation doesn't require the certificates to be stored in a file, instead - * it parses the given certificates of replica peers using Bouncycastle's X509CertParser - * and stores them in the in-memory KeyStore. + * It is seeded with the JVM default trust store (the 'javax.net.ssl.trustStore' + * file, or the 'cacerts' file of the Java installation) so that certificates + * signed by a trusted CA are accepted out of the box. Certificates of replica + * peers can be added on top of that by parsing them with Bouncycastle's + * X509CertParser and storing them in the in-memory KeyStore. * * The SunX509 TrustManagerFactory is then initialized using this KeyStore and the * resulting X509TrustManager present in this factory's TrustManagers will be used @@ -85,7 +90,7 @@ private ReplicationTrustManager() { try { - ks.load( null, null ); // initiate with null stream and password, this keystore resides in-memory only + loadDefaultTrustStore(); TrustManagerFactory tmFactory = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() ); tmFactory.init( ks ); @@ -110,6 +115,70 @@ private ReplicationTrustManager() } + /** + * Initializes the in-memory keystore with the JVM default trust store: + * either the file configured through the 'javax.net.ssl.trustStore' system + * property, or the 'cacerts' file of the Java installation. If none of + * them can be loaded, the keystore is left empty and certificates can + * still be added via {@link #addCertificate(String, byte[])}. + */ + private void loadDefaultTrustStore() + { + String trustStoreFile = System.getProperty( "javax.net.ssl.trustStore" ); + char[] trustStorePassword = null; + + String trustStorePasswordValue = System.getProperty( "javax.net.ssl.trustStorePassword" ); + + if ( trustStorePasswordValue != null ) + { + trustStorePassword = trustStorePasswordValue.toCharArray(); + } + + try + { + if ( trustStoreFile != null ) + { + try ( InputStream in = new FileInputStream( trustStoreFile ) ) + { + ks.load( in, trustStorePassword ); + } + } + else + { + File cacertsFile = new File( System.getProperty( "java.home" ), + "lib" + File.separator + "security" + File.separator + "cacerts" ); + + if ( cacertsFile.exists() ) + { + try ( InputStream in = new FileInputStream( cacertsFile ) ) + { + ks.load( in, trustStorePassword ); + } + } + else + { + ks.load( null, null ); + } + } + } + catch ( Exception e ) + { + LOG.warn( "failed to load the default trust store, continuing with an empty keystore", e ); + + try + { + ks = KeyStore.getInstance( KeyStore.getDefaultType() ); + ks.load( null, null ); + } + catch ( Exception ke ) + { + // cannot happen with a null stream and null password + LOG.error( "failed to initialize an empty keystore", ke ); + } + } + } + + /** * loads the given map of [alias-name, certificate-data] entries into the keystore * to be used by the trust manager diff --git a/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/SyncReplConfiguration.java b/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/SyncReplConfiguration.java index e5731e3af2..7671096d89 100644 --- a/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/SyncReplConfiguration.java +++ b/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/SyncReplConfiguration.java @@ -55,7 +55,8 @@ *
  • configEntryDn : the configuration entry's DN
  • *
  • chaseReferrals : tells if we chase referrals, defaults to false
  • *
  • cookie : the replication cookie
  • - *
  • useTls : the connection uses TLS, defaults to true
  • + *
  • useTls : the connection uses TLS (= StartTLS over LDAP), defaults to true
  • + *
  • useSsl : the connection uses SSL (= LDAPS), defaults to false
  • *
  • trustManager : the trustManager to use, defaults to {@link ReplicationTrustManager}
  • * * @@ -119,9 +120,12 @@ public class SyncReplConfiguration implements ReplicationConsumerConfig /** flag to indicate whether to chase referrals or not, default is false hence passes ManageDsaITControl with syncsearch request*/ private boolean chaseReferrals = false; - /** flag to indicate the use of TLS, default is true */ + /** flag to indicate the use of TLS (= StartTLS over LDAP), default is true */ private boolean useTls = true; + /** flag to indicate the use of SSL (= LDAPS), default is false */ + private boolean useSsl = false; + /** the X509 certificate trust manager used, default value set to {@link ReplicationTrustManager} */ private X509TrustManager trustManager = ReplicationTrustManager.getInstance(); @@ -489,7 +493,7 @@ public Dn getConfigEntryDn() /** - * @return true if we use TLS + * @return true if we use TLS (= StartTLS over LDAP) */ public boolean isUseTls() { @@ -498,9 +502,9 @@ public boolean isUseTls() /** - * set the option to turn on/off use of TLS + * set the option to turn on/off use of TLS (= StartTLS over LDAP) * - * @param useTls If we have to use TLS + * @param useTls If we have to use TLS (= StartTLS over LDAP) */ public void setUseTls( boolean useTls ) { @@ -508,6 +512,25 @@ public void setUseTls( boolean useTls ) } + /** + * @return true if we use SSL (= LDAPS) + */ + public boolean isUseSsl() + { + return useSsl; + } + + + /** + * set the option to turn on/off use of SSL (= LDAPS) + * + * @param useSsl If we have to use SSL (= LDAPS) + */ + public void setUseSsl( boolean useSsl ) + { + this.useSsl = useSsl; + } + /** * @return The Trustmanager instance */ @@ -643,7 +666,7 @@ public String toString() sb.append( "provider:" ).append( producer ).append( ", " ); sb.append( "user:'" ).append( replUserDn ).append( "', " ); - sb.append( "TLS:" ).append( useTls ).append( "]" ); + sb.append( "TLS:" ).append( useTls ).append( ", SSL:" ).append( useSsl ).append( "]" ); return sb.toString(); } diff --git a/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/consumer/ReplicationConsumerImpl.java b/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/consumer/ReplicationConsumerImpl.java index e2c292f783..8d7902623e 100644 --- a/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/consumer/ReplicationConsumerImpl.java +++ b/protocol-ldap/src/main/java/org/apache/directory/server/ldap/replication/consumer/ReplicationConsumerImpl.java @@ -222,7 +222,12 @@ public boolean connect() connection = new LdapNetworkConnection( providerHost, port ); connection.setSchemaManager( schemaManager ); - if ( config.isUseTls() ) + if ( config.isUseSsl() ) + { + connection.getConfig().setTrustManagers( config.getTrustManager() ); + connection.getConfig().setUseSsl( true ); + } + else if ( config.isUseTls() ) { connection.getConfig().setTrustManagers( config.getTrustManager() ); connection.getConfig().setUseTls( true ); diff --git a/server-annotations/src/main/java/org/apache/directory/server/annotations/CreateConsumer.java b/server-annotations/src/main/java/org/apache/directory/server/annotations/CreateConsumer.java index a2a3764a09..52333a39e3 100644 --- a/server-annotations/src/main/java/org/apache/directory/server/annotations/CreateConsumer.java +++ b/server-annotations/src/main/java/org/apache/directory/server/annotations/CreateConsumer.java @@ -53,6 +53,7 @@ *
  • configEntryDn : the configuration entry's DN
  • *
  • chaseReferrals : tells if we chase referrals, defaults to false
  • *
  • useTls : the connection uses TLS, defaults to true
  • + *
  • useSsl : the connection uses SSL (= LDAPS), defaults to false
  • *
  • trustManagerFQCN : X509TrustManager FQCN to override strictCertVerification
  • * * @author Apache Directory Project @@ -199,6 +200,14 @@ boolean useTls() default true; + /** + * flag to indicate the use of SSL (= LDAPS), default is false + * + * @return true if SSL is in use + */ + boolean useSsl() default false; + + /** * Fully-qualified class name of an X509TrustManager to use for replication TLS. * If set, this takes precedence over strictCertVerification(). diff --git a/server-annotations/src/main/java/org/apache/directory/server/factory/ServerAnnotationProcessor.java b/server-annotations/src/main/java/org/apache/directory/server/factory/ServerAnnotationProcessor.java index fbf1ab2d1f..ec66535b5d 100644 --- a/server-annotations/src/main/java/org/apache/directory/server/factory/ServerAnnotationProcessor.java +++ b/server-annotations/src/main/java/org/apache/directory/server/factory/ServerAnnotationProcessor.java @@ -294,6 +294,7 @@ private static ReplicationConsumer createConsumer( CreateConsumer createConsumer config.setReplUserDn( createConsumer.replUserDn() ); config.setReplUserPassword( Strings.getBytesUtf8( createConsumer.replUserPassword() ) ); config.setUseTls( createConsumer.useTls() ); + config.setUseSsl( createConsumer.useSsl() ); config.setBaseDn( createConsumer.baseDn() ); config.setRefreshInterval( createConsumer.refreshInterval() ); diff --git a/server-config/src/main/java/org/apache/directory/server/config/beans/ReplConsumerBean.java b/server-config/src/main/java/org/apache/directory/server/config/beans/ReplConsumerBean.java index 20f66c5ef9..6dbe7ea6da 100644 --- a/server-config/src/main/java/org/apache/directory/server/config/beans/ReplConsumerBean.java +++ b/server-config/src/main/java/org/apache/directory/server/config/beans/ReplConsumerBean.java @@ -98,6 +98,10 @@ public class ReplConsumerBean extends AdsBaseBean @ConfigurationElement(attributeType = SchemaConstants.ADS_REPL_USE_TLS, isOptional = true) private boolean replUseTls; + /** Tells if SSL should be used during replication */ + @ConfigurationElement(attributeType = SchemaConstants.ADS_REPL_USE_SSL, isOptional = true, defaultValue = "false") + private boolean replUseSsl; + /** The X509 TrustManager FQCN to use */ @ConfigurationElement(attributeType = "ads-replTrustManagerFQCN", isOptional = true) private String replTrustManagerFQCN; @@ -419,6 +423,24 @@ public void setReplUseTls( boolean replUseTls ) } + /** + * @return the replUseSsl + */ + public boolean isReplUseSsl() + { + return replUseSsl; + } + + + /** + * @param replUseSsl the replUseSsl to set + */ + public void setReplUseSsl( boolean replUseSsl ) + { + this.replUseSsl = replUseSsl; + } + + /** * @return the replTrustManagerFQCN */ @@ -485,6 +507,7 @@ public String toString( String tabs ) sb.append( toString( tabs, " trust manager's FQCN", replTrustManagerFQCN ) ); sb.append( toString( tabs, " use TLS", replUseTls ) ); + sb.append( toString( tabs, " use SSL", replUseSsl ) ); return sb.toString(); } diff --git a/server-config/src/test/java/org/apache/directory/server/config/ConfigWriterTest.java b/server-config/src/test/java/org/apache/directory/server/config/ConfigWriterTest.java index a3962cad1d..48c454b51c 100644 --- a/server-config/src/test/java/org/apache/directory/server/config/ConfigWriterTest.java +++ b/server-config/src/test/java/org/apache/directory/server/config/ConfigWriterTest.java @@ -22,10 +22,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -44,6 +46,10 @@ import org.apache.directory.api.util.FileUtils; import org.apache.directory.api.util.exception.Exceptions; import org.apache.directory.server.config.beans.ConfigBean; +import org.apache.directory.server.config.beans.AdsBaseBean; +import org.apache.directory.server.config.beans.DirectoryServiceBean; +import org.apache.directory.server.config.beans.LdapServerBean; +import org.apache.directory.server.config.beans.ReplConsumerBean; import org.apache.directory.server.core.api.DnFactory; import org.apache.directory.server.core.partition.ldif.SingleFileLdifPartition; import org.apache.directory.server.core.shared.DefaultDnFactory; @@ -162,4 +168,69 @@ public int compare( LdifEntry o1, LdifEntry o2 ) // Destroying the config partition configPartition.destroy( configPartition.beginReadTransaction() ); } + + + /** + * Tests that the 'ads-replUseSsl' attribute is not written into the + * configuration entries as long as it keeps its default value (false), + * and that it is written once explicitly enabled. The attribute type + * itself is defined in the LDAP API schema. + */ + @Test + public void testReplConsumerUseSsl() throws Exception + { + ConfigBean configBean = new ConfigBean(); + + // The list is normally set by the ConfigPartitionReader, initialize it here + Field directoryServiceBeansField = ConfigBean.class.getDeclaredField( "directoryServiceBeans" ); + directoryServiceBeansField.setAccessible( true ); + directoryServiceBeansField.set( configBean, new ArrayList() ); + + DirectoryServiceBean directoryServiceBean = new DirectoryServiceBean(); + directoryServiceBean.setDirectoryServiceId( "default" ); + + LdapServerBean ldapServerBean = new LdapServerBean(); + ldapServerBean.setServerId( "ldapServer" ); + + ReplConsumerBean replConsumerBean = new ReplConsumerBean(); + replConsumerBean.setReplConsumerId( "test" ); + replConsumerBean.setReplProvHostName( "localhost" ); + replConsumerBean.setReplProvPort( 10389 ); + replConsumerBean.setSearchBaseDn( "ou=system" ); + + ldapServerBean.addReplConsumers( replConsumerBean ); + directoryServiceBean.addServers( ldapServerBean ); + configBean.addDirectoryService( directoryServiceBean ); + + // With the default value (false), the attribute must not be written + ConfigWriter configWriter = new ConfigWriter( schemaManager, configBean ); + LdifEntry replConsumerEntry = findReplConsumerEntry( configWriter.getConvertedLdifEntries() ); + assertNotNull( replConsumerEntry ); + assertFalse( replConsumerEntry.getEntry().containsAttribute( "ads-replUseSsl" ) ); + + // With the flag explicitly enabled, the attribute must be written + replConsumerBean.setReplUseSsl( true ); + configWriter = new ConfigWriter( schemaManager, configBean ); + replConsumerEntry = findReplConsumerEntry( configWriter.getConvertedLdifEntries() ); + assertNotNull( replConsumerEntry ); + assertTrue( replConsumerEntry.getEntry().containsAttribute( "ads-replUseSsl" ) ); + assertTrue( Boolean.parseBoolean( replConsumerEntry.getEntry().get( "ads-replUseSsl" ).getString() ) ); + } + + + /** + * Finds the replication consumer entry in the given list of entries. + */ + private LdifEntry findReplConsumerEntry( List entries ) + { + for ( LdifEntry entry : entries ) + { + if ( "ads-replConsumerId=test".equalsIgnoreCase( entry.getDn().getRdn().getName() ) ) + { + return entry; + } + } + + return null; + } } diff --git a/server-config/src/test/resources/ldapServer.ldif b/server-config/src/test/resources/ldapServer.ldif index 4d3c625747..2a24f0b616 100644 --- a/server-config/src/test/resources/ldapServer.ldif +++ b/server-config/src/test/resources/ldapServer.ldif @@ -180,6 +180,7 @@ ads-replSearchFilter: (&(objectClass=top)(ou=test)) ads-replProvPort: 12345 ads-replRefreshNPersist: TRUE ads-replUseTls: TRUE +ads-replUseSsl: TRUE ads-replStrictCertValidation: TRUE ads-replAliasDerefMode: NEVER ads-replRefreshInterval: 100 diff --git a/service-builder/src/main/java/org/apache/directory/server/config/builder/ServiceBuilder.java b/service-builder/src/main/java/org/apache/directory/server/config/builder/ServiceBuilder.java index a105231067..1c8e503847 100644 --- a/service-builder/src/main/java/org/apache/directory/server/config/builder/ServiceBuilder.java +++ b/service-builder/src/main/java/org/apache/directory/server/config/builder/ServiceBuilder.java @@ -1074,6 +1074,7 @@ public static List createReplConsumers( List