diff --git a/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/sasl/external/certificate/ExternalSaslServer.java b/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/sasl/external/certificate/ExternalSaslServer.java
index 48a5e0dc22..c1526da6d3 100644
--- a/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/sasl/external/certificate/ExternalSaslServer.java
+++ b/protocol-ldap/src/main/java/org/apache/directory/server/ldap/handlers/sasl/external/certificate/ExternalSaslServer.java
@@ -39,7 +39,6 @@
import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
import org.apache.directory.server.core.api.interceptor.context.BindOperationContext;
import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
-import org.apache.directory.server.ldap.LdapServer;
import org.apache.directory.server.ldap.LdapSession;
import org.apache.directory.server.ldap.handlers.sasl.AbstractSaslServer;
import org.apache.directory.server.ldap.handlers.sasl.SaslConstants;
@@ -137,11 +136,29 @@ public boolean isComplete()
* We identify the user using the provided peercertificate.
*/
private CoreSession authenticate( Certificate peerCertificate ) throws Exception
+ {
+ // search for client certificate from base dn
+ CoreSession session = searchUserWithCertificate( peerCertificate, getLdapSession().getLdapServer().getSearchBaseDn() );
+ if ( session != null )
+ {
+ return session;
+ }
+
+ // search for client certificate from admin user
+ session = searchUserWithCertificate( peerCertificate, "uid=admin,ou=system" );
+ if ( session != null )
+ {
+ return session;
+ }
+
+ throw new LdapAuthenticationException( "Cannot authenticate user cert=" + peerCertificate );
+ }
+
+ private CoreSession searchUserWithCertificate( Certificate peerCertificate, String baseDn ) throws Exception
{
LdapSession ldapSession = getLdapSession();
CoreSession adminSession = getAdminSession();
DirectoryService directoryService = adminSession.getDirectoryService();
- LdapServer ldapServer = ldapSession.getLdapServer();
OperationManager operationManager = directoryService.getOperationManager();
// find user by userCertificate
@@ -150,7 +167,7 @@ private CoreSession authenticate( Certificate peerCertificate ) throws Exception
new Value( peerCertificate.getEncoded() ) );
SearchOperationContext searchContext = new SearchOperationContext( directoryService.getAdminSession() );
- searchContext.setDn( directoryService.getDnFactory().create( ldapServer.getSearchBaseDn() ) );
+ searchContext.setDn( directoryService.getDnFactory().create( baseDn ) );
searchContext.setScope( SearchScope.SUBTREE );
searchContext.setFilter( filter );
searchContext.setSizeLimit( 1 );
@@ -177,8 +194,8 @@ private CoreSession authenticate( Certificate peerCertificate ) throws Exception
return bindContext.getSession();
}
-
- throw new LdapAuthenticationException( "Cannot authenticate user cert=" + peerCertificate );
}
+
+ return null;
}
-}
\ No newline at end of file
+}
diff --git a/server-integ/src/test/java/org/apache/directory/server/ldap/handlers/sasl/external/ClientCertificateAdminAuthenticationIT.java b/server-integ/src/test/java/org/apache/directory/server/ldap/handlers/sasl/external/ClientCertificateAdminAuthenticationIT.java
new file mode 100644
index 0000000000..e1de7d3bb4
--- /dev/null
+++ b/server-integ/src/test/java/org/apache/directory/server/ldap/handlers/sasl/external/ClientCertificateAdminAuthenticationIT.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.directory.server.ldap.handlers.sasl.external;
+
+import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms;
+import org.apache.directory.api.ldap.model.entry.DefaultEntry;
+import org.apache.directory.api.ldap.model.entry.DefaultModification;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.entry.Modification;
+import org.apache.directory.api.ldap.model.entry.ModificationOperation;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.directory.api.util.Network;
+import org.apache.directory.server.annotations.CreateLdapServer;
+import org.apache.directory.server.annotations.CreateTransport;
+import org.apache.directory.server.annotations.SaslMechanism;
+import org.apache.directory.server.core.annotations.*;
+import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
+import org.apache.directory.server.core.integ.FrameworkRunner;
+import org.apache.directory.server.core.security.TlsKeyGenerator;
+import org.apache.directory.server.ldap.handlers.sasl.external.certificate.CertificateMechanismHandler;
+import org.apache.directory.server.ssl.ClientCertificateSslSocketFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import java.io.ByteArrayInputStream;
+import java.io.FileOutputStream;
+import java.net.InetAddress;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.util.Date;
+import java.util.Hashtable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test the authentication using EXTERNAL SASL client certificate authentication.
+ * Stores the client certificate on the admin which is also used for ldap connection.
+ *
+ * @author Apache Directory Project
+ */
+@RunWith(FrameworkRunner.class)
+@CreateDS(allowAnonAccess = true, name = "ClientCertificateAdminAuthenticationIT-class",
+ partitions =
+ {
+ @CreatePartition(
+ name = "example",
+ suffix = "dc=example,dc=com",
+ contextEntry = @ContextEntry(
+ entryLdif =
+ "dn: dc=example,dc=com\n" +
+ "dc: example\n" +
+ "objectClass: top\n" +
+ "objectClass: domain\n\n"),
+ indexes =
+ {
+ @CreateIndex(attribute = "objectClass"),
+ @CreateIndex(attribute = "dc"),
+ @CreateIndex(attribute = "ou")
+ })
+ })
+@CreateLdapServer(
+ transports =
+ {
+ @CreateTransport(protocol = "LDAPS", clientAuth = true)
+ },
+ saslMechanisms =
+ {
+ @SaslMechanism(name = SupportedSaslMechanisms.EXTERNAL, implClass = CertificateMechanismHandler.class)
+ })
+@ApplyLdifs(
+ {
+ // Entry # 1
+ "dn: ou=users,dc=example,dc=com",
+ "objectClass: organizationalUnit",
+ "objectClass: top",
+ "ou: users\n",
+
+ // Entry # 2
+ "dn: uid=testsubject,ou=users,dc=example,dc=com",
+ "objectClass: inetOrgPerson",
+ "objectClass: organizationalPerson",
+ "objectClass: person",
+ "objectClass: top",
+ "uid: testsubject",
+ "userPassword: not_set",
+ "cn: Test Subject",
+ "sn: Subject"
+ })
+
+public class ClientCertificateAdminAuthenticationIT extends AbstractLdapTestUnit
+{
+
+ private Dn authenticationUserDn;
+
+ /**
+ * Setup the test, prepare certificate and add userCertificate attribute for admin
+ * @throws Exception on any error
+ */
+ @Before
+ public void installKeyStoreWithCertificate() throws Exception
+ {
+ authenticationUserDn = new Dn("uid=admin,ou=system");
+
+ String hostName = InetAddress.getLocalHost().getHostName();
+ String issuerDn = TlsKeyGenerator.CERTIFICATE_PRINCIPAL_DN;
+ String subjectDn = "CN=" + hostName;
+ Date startDate = new Date();
+ Date expiryDate = new Date( System.currentTimeMillis() + TlsKeyGenerator.YEAR_MILLIS );
+ String keyAlgo = "RSA";
+ int keySize = 1024;
+
+ Entry entry = new DefaultEntry();
+ TlsKeyGenerator.addKeyPair( entry, issuerDn, subjectDn, startDate, expiryDate, keyAlgo, keySize, null, false );
+
+ // prepare socket factory to provide client certificate
+ try (
+ ByteArrayInputStream in = new ByteArrayInputStream( TlsKeyGenerator.getCertificate( entry ).getEncoded() );
+ FileOutputStream out = new FileOutputStream( ClientCertificateSslSocketFactory.ksFile ) )
+ {
+ CertificateFactory factory = CertificateFactory.getInstance( "X.509" );
+ Certificate cert = factory.generateCertificate( in );
+ KeyStore ks = KeyStore.getInstance( KeyStore.getDefaultType() );
+ ks.load( null, null );
+ ks.setKeyEntry("apacheds", TlsKeyGenerator.getKeyPair( entry ).getPrivate(), ClientCertificateSslSocketFactory.ksPassword, new Certificate[] { cert } );
+ ks.store( out, ClientCertificateSslSocketFactory.ksPassword );
+ }
+
+ // set certificte attribute to admin
+ Modification mod = new DefaultModification( ModificationOperation.ADD_ATTRIBUTE,
+ TlsKeyGenerator.USER_CERTIFICATE_AT, entry.get( TlsKeyGenerator.USER_CERTIFICATE_AT ).getBytes() );
+ getLdapServer().getDirectoryService().getAdminSession().modify( authenticationUserDn, mod );
+ }
+
+ /**
+ * Cleanup test, remove keystore
+ * @throws Exception on any error
+ */
+ @After
+ public void teardown() throws Exception {
+ if ( ClientCertificateSslSocketFactory.ksFile != null && ClientCertificateSslSocketFactory.ksFile.exists() )
+ {
+ ClientCertificateSslSocketFactory.ksFile.delete();
+ }
+ }
+
+ /**
+ * Do just a connect and a simple search to verify if authentication works.
+ * The test checks the authentication user in the current ldap session.
+ *
+ * @throws Exception on any error
+ */
+ @Test
+ public void testExternalClientCertificateAdminAuthentication() throws Exception
+ {
+ // create a new secure connection
+ Hashtable