| /* |
| * 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.kerberos.kdc; |
| |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.fail; |
| |
| import java.io.IOException; |
| import java.security.PrivilegedAction; |
| import java.util.Hashtable; |
| |
| import javax.naming.Context; |
| import javax.naming.NamingException; |
| import javax.naming.directory.Attributes; |
| import javax.naming.directory.DirContext; |
| import javax.naming.directory.InitialDirContext; |
| import javax.security.auth.Subject; |
| import javax.security.auth.callback.Callback; |
| import javax.security.auth.callback.CallbackHandler; |
| import javax.security.auth.callback.NameCallback; |
| import javax.security.auth.callback.PasswordCallback; |
| import javax.security.auth.callback.UnsupportedCallbackException; |
| import javax.security.auth.login.Configuration; |
| import javax.security.auth.login.LoginContext; |
| import javax.security.auth.login.LoginException; |
| |
| import org.apache.directory.api.ldap.model.constants.SupportedSaslMechanisms; |
| import org.apache.directory.ldap.client.api.Krb5LoginConfiguration; |
| import org.apache.directory.server.annotations.CreateKdcServer; |
| 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.ApplyLdifs; |
| import org.apache.directory.server.core.annotations.ContextEntry; |
| import org.apache.directory.server.core.annotations.CreateDS; |
| import org.apache.directory.server.core.annotations.CreateIndex; |
| import org.apache.directory.server.core.annotations.CreatePartition; |
| import org.apache.directory.server.core.integ.FrameworkRunner; |
| import org.apache.directory.server.core.kerberos.KeyDerivationInterceptor; |
| import org.apache.directory.server.i18n.I18n; |
| import org.apache.directory.server.ldap.handlers.sasl.cramMD5.CramMd5MechanismHandler; |
| import org.apache.directory.server.ldap.handlers.sasl.digestMD5.DigestMd5MechanismHandler; |
| import org.apache.directory.server.ldap.handlers.sasl.gssapi.GssapiMechanismHandler; |
| import org.apache.directory.server.ldap.handlers.sasl.ntlm.NtlmMechanismHandler; |
| import org.apache.directory.server.ldap.handlers.sasl.plain.PlainMechanismHandler; |
| import org.apache.directory.server.protocol.shared.transport.TcpTransport; |
| import org.apache.directory.shared.kerberos.codec.types.EncryptionType; |
| import org.apache.directory.shared.kerberos.crypto.checksum.ChecksumType; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| |
| /** |
| * An {@link AbstractServerTest} testing SASL GSSAPI authentication |
| * and security layer negotiation. These tests require both the LDAP |
| * and the Kerberos protocol. As with any "three-headed" Kerberos |
| * scenario, there are 3 principals: 1 for the test user, 1 for the |
| * Kerberos ticket-granting service (TGS), and 1 for the LDAP service. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| @RunWith(FrameworkRunner.class) |
| @CreateDS(name = "SaslGssapiBindITest-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") |
| }) |
| }, |
| additionalInterceptors = |
| { |
| KeyDerivationInterceptor.class |
| }) |
| @CreateLdapServer( |
| transports = |
| { |
| @CreateTransport(protocol = "LDAP") |
| }, |
| saslHost = "localhost", |
| saslPrincipal = "ldap/localhost@EXAMPLE.COM", |
| saslMechanisms = |
| { |
| @SaslMechanism(name = SupportedSaslMechanisms.PLAIN, implClass = PlainMechanismHandler.class), |
| @SaslMechanism(name = SupportedSaslMechanisms.CRAM_MD5, implClass = CramMd5MechanismHandler.class), |
| @SaslMechanism(name = SupportedSaslMechanisms.DIGEST_MD5, implClass = DigestMd5MechanismHandler.class), |
| @SaslMechanism(name = SupportedSaslMechanisms.GSSAPI, implClass = GssapiMechanismHandler.class), |
| @SaslMechanism(name = SupportedSaslMechanisms.NTLM, implClass = NtlmMechanismHandler.class), |
| @SaslMechanism(name = SupportedSaslMechanisms.GSS_SPNEGO, implClass = NtlmMechanismHandler.class) |
| }) |
| @CreateKdcServer( |
| transports = |
| { |
| @CreateTransport(protocol = "UDP", port = 6088), |
| @CreateTransport(protocol = "TCP", port = 6088) |
| }) |
| @ApplyLdifs({ |
| "dn: ou=users,dc=example,dc=com", |
| "objectClass: top", |
| "objectClass: organizationalUnit", |
| "ou: users" |
| }) |
| public class SaslGssapiBindITest extends AbstractKerberosITest |
| { |
| |
| /** |
| * Tests to make sure GSSAPI binds below the RootDSE work. |
| */ |
| @Test |
| public void testSaslGssapiBind() throws Exception |
| { |
| // Set up a partition for EXAMPLE.COM and add user and service principals to test authentication with. |
| KerberosTestUtils.fixServicePrincipalName( |
| "ldap/" + KerberosTestUtils.getHostName() + "@EXAMPLE.COM", null, getLdapServer() ); |
| ObtainTicketParameters parameters = new ObtainTicketParameters( TcpTransport.class, |
| EncryptionType.AES128_CTS_HMAC_SHA1_96, ChecksumType.HMAC_SHA1_96_AES128 ); |
| setupEnv( parameters ); |
| |
| kdcServer.getConfig().setPaEncTimestampRequired( false ); |
| // Use our custom configuration to avoid reliance on external config |
| Configuration.setConfiguration( new Krb5LoginConfiguration() ); |
| // 1. Authenticate to Kerberos. |
| LoginContext lc = null; |
| try |
| { |
| lc = new LoginContext( SaslGssapiBindITest.class.getName(), new CallbackHandlerBean( "hnelson", "secret" ) ); |
| lc.login(); |
| } |
| catch ( LoginException le ) |
| { |
| // Bad username: Client not found in Kerberos database |
| // Bad password: Integrity check on decrypted field failed |
| fail( "Authentication failed: " + le.getMessage() ); |
| } |
| |
| // 2. Perform JNDI work as authenticated Subject. |
| Subject.doAs( lc.getSubject(), new PrivilegedAction<Void>() |
| { |
| public Void run() |
| { |
| //FIXME activate this code as soon as the GSSAPIMechanismHandler is fixed. |
| //Currently GSSAPI authentication for the ldap server is broken |
| try |
| { |
| // Create the initial context |
| Hashtable<String, String> env = new Hashtable<String, String>(); |
| env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" ); |
| env.put( Context.PROVIDER_URL, "ldap://" + KerberosTestUtils.getHostName() + ":" |
| + getLdapServer().getPort() ); |
| |
| // Request the use of the "GSSAPI" SASL mechanism |
| // Authenticate by using already established Kerberos credentials |
| env.put( Context.SECURITY_AUTHENTICATION, "GSSAPI" ); |
| |
| // Request privacy protection |
| env.put( "javax.security.sasl.qop", "auth-conf" ); |
| |
| // Request mutual authentication |
| env.put( "javax.security.sasl.server.authentication", "true" ); |
| |
| // Request high-strength cryptographic protection |
| env.put( "javax.security.sasl.strength", "high" ); |
| |
| DirContext ctx = new InitialDirContext( env ); |
| |
| String[] attrIDs = |
| { "uid" }; |
| |
| Attributes attrs = ctx.getAttributes( "uid=hnelson,ou=users,dc=example,dc=com", attrIDs ); |
| |
| String uid = null; |
| |
| if ( attrs.get( "uid" ) != null ) |
| { |
| uid = ( String ) attrs.get( "uid" ).get(); |
| } |
| |
| assertEquals( "hnelson", uid ); |
| } |
| catch ( NamingException e ) |
| { |
| fail( "Should not have caught exception: " + e.getMessage() + e.getRootCause() ); |
| } |
| |
| return null; |
| } |
| } ); |
| |
| } |
| |
| |
| private class CallbackHandlerBean implements CallbackHandler |
| { |
| private String name; |
| private String password; |
| |
| |
| /** |
| * Creates a new instance of CallbackHandlerBean. |
| * |
| * @param name |
| * @param password |
| */ |
| public CallbackHandlerBean( String name, String password ) |
| { |
| this.name = name; |
| this.password = password; |
| } |
| |
| |
| public void handle( Callback[] callbacks ) throws UnsupportedCallbackException, IOException |
| { |
| for ( int ii = 0; ii < callbacks.length; ii++ ) |
| { |
| Callback callBack = callbacks[ii]; |
| |
| // Handles username callback. |
| if ( callBack instanceof NameCallback ) |
| { |
| NameCallback nameCallback = ( NameCallback ) callBack; |
| nameCallback.setName( name ); |
| // Handles password callback. |
| } |
| else if ( callBack instanceof PasswordCallback ) |
| { |
| PasswordCallback passwordCallback = ( PasswordCallback ) callBack; |
| passwordCallback.setPassword( password.toCharArray() ); |
| } |
| else |
| { |
| throw new UnsupportedCallbackException( callBack, I18n.err( I18n.ERR_617 ) ); |
| } |
| } |
| } |
| } |
| |
| } |