blob: 668278e7f911e90d48e30b8cc6e8b7301d9e9a35 [file] [log] [blame]
/*
* 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.karaf.jaas.modules.krb5;
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.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
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.kerberos.kdc.KerberosTestUtils;
import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
import org.apache.directory.server.kerberos.shared.keytab.Keytab;
import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
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.server.protocol.shared.transport.Transport;
import org.apache.directory.shared.kerberos.KerberosTime;
import org.apache.directory.shared.kerberos.KerberosUtils;
import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
import org.apache.directory.shared.kerberos.components.EncryptionKey;
import org.apache.directory.shared.kerberos.crypto.checksum.ChecksumType;
import org.apache.karaf.jaas.modules.NamePasswordCallbackHandler;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.kerberos.KerberosTicket;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginException;
import java.io.File;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.apache.karaf.jaas.modules.PrincipalHelper.names;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@RunWith(FrameworkRunner.class)
@CreateDS(name = "Krb5LoginModuleTest-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 Krb5LoginModuleTest extends KarafKerberosITest {
@Before
public void setUp() throws Exception {
super.setUp();
setupEnv(TcpTransport.class,
EncryptionType.AES128_CTS_HMAC_SHA1_96, ChecksumType.HMAC_SHA1_96_AES128);
// 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());
kdcServer.getConfig().setPaEncTimestampRequired(false);
// Use our custom configuration to avoid reliance on external config
Configuration.setConfiguration(new Krb5LoginConfiguration());
}
@After
public void tearDown() throws Exception {
super.tearDown();
}
@Test
public void testKeytabSuccess() throws Exception {
Map<String, Object> props = new HashMap<>();
props.put("debug", "true");
props.put("useKeyTab", "true");
props.put("keyTab", createKeytab());
props.put("principal", "hnelson@EXAMPLE.COM");
props.put("doNotPrompt", "true");
props.put("storeKey", "true");
props.put("detailed.login.exception", "true");
Subject subject = new Subject();
Krb5LoginModule module = new Krb5LoginModule();
module.initialize(subject, null, null, props);
assertEquals("Precondition", 0, subject.getPrincipals().size());
Assert.assertTrue(module.login());
Assert.assertTrue(module.commit());
assertEquals(1, subject.getPrincipals().size());
assertThat(names(subject.getPrincipals(KerberosPrincipal.class)), containsInAnyOrder("hnelson@EXAMPLE.COM"));
KerberosTicket ticket = subject.getPrivateCredentials(KerberosTicket.class).iterator().next();
assertEquals("hnelson@EXAMPLE.COM", ticket.getClient().getName());
assertEquals("krbtgt/EXAMPLE.COM@EXAMPLE.COM", ticket.getServer().getName());
Assert.assertTrue(module.logout());
}
@Test(expected = LoginException.class)
public void testKeytabFailure() throws Exception {
Map<String, Object> props = new HashMap<>();
props.put("debug", "true");
props.put("useKeyTab", "true");
props.put("keyTab", createKeytab());
props.put("principal", "hnelson0@EXAMPLE.COM");
props.put("doNotPrompt", "true");
props.put("storeKey", "true");
props.put("detailed.login.exception", "true");
Subject subject = new Subject();
Krb5LoginModule module = new Krb5LoginModule();
module.initialize(subject, null, null, props);
assertEquals("Precondition", 0, subject.getPrincipals().size());
Assert.assertFalse(module.login());
}
@Test
public void testLoginSuccess() throws Exception {
Subject subject = new Subject();
Krb5LoginModule module = new Krb5LoginModule();
module.initialize(subject, new NamePasswordCallbackHandler("hnelson", "secret"), null, new HashMap<>());
assertEquals("Precondition", 0, subject.getPrincipals().size());
Assert.assertTrue(module.login());
Assert.assertTrue(module.commit());
assertEquals(1, subject.getPrincipals().size());
assertThat(names(subject.getPrincipals(KerberosPrincipal.class)), containsInAnyOrder("hnelson@EXAMPLE.COM"));
KerberosTicket ticket = subject.getPrivateCredentials(KerberosTicket.class).iterator().next();
assertEquals("hnelson@EXAMPLE.COM", ticket.getClient().getName());
assertEquals("krbtgt/EXAMPLE.COM@EXAMPLE.COM", ticket.getServer().getName());
Assert.assertTrue(module.logout());
}
@Test(expected = LoginException.class)
public void testLoginUsernameFailure() throws Exception {
Subject subject = new Subject();
Krb5LoginModule module = new Krb5LoginModule();
module.initialize(subject, new NamePasswordCallbackHandler("hnelson0", "secret"), null, new HashMap<>());
assertEquals("Precondition", 0, subject.getPrincipals().size());
Assert.assertFalse(module.login());
}
@Test(expected = LoginException.class)
public void testLoginPasswordFailure() throws Exception {
Subject subject = new Subject();
Krb5LoginModule module = new Krb5LoginModule();
module.initialize(subject, new NamePasswordCallbackHandler("hnelson", "secret0"), null, new HashMap<>());
assertEquals("Precondition", 0, subject.getPrincipals().size());
Assert.assertFalse(module.login());
}
protected void setupEnv(Class<? extends Transport> transport, EncryptionType encryptionType,
ChecksumType checksumType)
throws Exception {
// create krb5.conf with proper encryption type
String krb5confPath = createKrb5Conf(checksumType, encryptionType, transport == TcpTransport.class);
System.setProperty("java.security.krb5.conf", krb5confPath);
// change encryption type in KDC
kdcServer.getConfig().setEncryptionTypes(Collections.singleton(encryptionType));
// create principals
createPrincipal("uid=" + USER_UID, "Last", "First Last",
USER_UID, USER_PASSWORD, USER_UID + "@" + REALM);
createPrincipal("uid=krbtgt", "KDC Service", "KDC Service",
"krbtgt", "secret", "krbtgt/" + REALM + "@" + REALM);
String servicePrincipal = LDAP_SERVICE_NAME + "/" + HOSTNAME + "@" + REALM;
createPrincipal("uid=ldap", "Service", "LDAP Service",
"ldap", "randall", servicePrincipal);
}
private void createPrincipal(String rdn, String sn, String cn,
String uid, String userPassword, String principalName) throws LdapException {
Entry entry = new DefaultEntry();
entry.setDn(rdn + "," + USERS_DN);
entry.add("objectClass", "top", "person", "inetOrgPerson", "krb5principal", "krb5kdcentry");
entry.add("cn", cn);
entry.add("sn", sn);
entry.add("uid", uid);
entry.add("userPassword", userPassword);
entry.add("krb5PrincipalName", principalName);
entry.add("krb5KeyVersionNumber", "0");
conn.add(entry);
}
private KeytabEntry createKeytabEntry() throws ParseException {
String principalName = "hnelson@EXAMPLE.COM";
int principalType = 1;
String zuluTime = "20070217235745Z";
Date date = null;
synchronized (KerberosUtils.UTC_DATE_FORMAT) {
date = KerberosUtils.UTC_DATE_FORMAT.parse(zuluTime);
}
KerberosTime timeStamp = new KerberosTime(date.getTime());
byte keyVersion = 1;
String passPhrase = "secret";
Map<EncryptionType, EncryptionKey> keys = KerberosKeyFactory.getKerberosKeys(principalName, passPhrase);
EncryptionKey key = keys.get(EncryptionType.AES128_CTS_HMAC_SHA1_96);
return new KeytabEntry(principalName, principalType, timeStamp, keyVersion, key);
}
private String createKeytab() throws Exception {
File file = folder.newFile("test.keytab");
List<KeytabEntry> entries = new ArrayList<>();
entries.add(createKeytabEntry());
Keytab writer = Keytab.getInstance();
writer.setEntries(entries);
writer.write(file);
return file.getAbsolutePath();
}
}