Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow admin to authenticate with client certificate #39

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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 );
Expand All @@ -177,8 +194,8 @@ private CoreSession authenticate( Certificate peerCertificate ) throws Exception

return bindContext.getSession();
}

throw new LdapAuthenticationException( "Cannot authenticate user cert=" + peerCertificate );
}

return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -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 <a href="mailto:[email protected]">Apache Directory Project</a>
*/
@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<Object, Object> env = new Hashtable<>();
env.put( "java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory" );
env.put( "java.naming.provider.url", Network.ldapLoopbackUrl( getLdapServer().getPortSSL() ) );
env.put( "java.naming.security.protocol", "ssl");
env.put( "java.naming.ldap.factory.socket", ClientCertificateSslSocketFactory.class.getName () );
env.put( "java.naming.security.authentication", "EXTERNAL" );

DirContext ctx = new InitialDirContext( env );
try
{
String searchFilter = "(objectClass=*)";
SearchControls searchControls = new SearchControls();
searchControls.setSearchScope( SearchControls.OBJECT_SCOPE );

NamingEnumeration<SearchResult> results = ctx.search( "dc=example,dc=com", searchFilter, searchControls );
assertTrue( results.hasMore() );

assertEquals(authenticationUserDn.getName(),
getLdapServer().getLdapSessionManager().getSessions()[0].getCoreSession().getAuthenticatedPrincipal().getDn().getName());
}
finally
{
ctx.close();
}
}

}