/*
* JBoss, Home of Professional Open Source
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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.wildfly.security.ldap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.auth.realm.ldap.LdapSecurityRealmBuilder;
import org.wildfly.security.auth.server.ModifiableRealmIdentity;
import org.wildfly.security.auth.server.RealmIdentity;
import org.wildfly.security.auth.server.RealmUnavailableException;
import org.wildfly.security.auth.server.SecurityRealm;
import org.wildfly.security.auth.SupportLevel;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.password.Password;
import org.wildfly.security.password.PasswordFactory;
import org.wildfly.security.password.interfaces.BSDUnixDESCryptPassword;
import org.wildfly.security.password.interfaces.ClearPassword;
import org.wildfly.security.password.interfaces.OneTimePassword;
import org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword;
import org.wildfly.security.password.interfaces.SimpleDigestPassword;
import org.wildfly.security.password.interfaces.UnixDESCryptPassword;
import org.wildfly.security.password.spec.ClearPasswordSpec;
import org.wildfly.security.password.spec.OneTimePasswordSpec;
/**
* Test case to test access to passwords stored in LDAP using the 'userPassword' attribute.
*
* This test case use {@link DirContextFactoryRule} to ensure running embedded LDAP server.
*
* Note: Verify {@link TestEnvironmentSuiteChild} is working first before focusing on errors in this test case.
*
* @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class PasswordSupportSuiteChild {
private static SecurityRealm simpleToDnRealm;
@BeforeClass
public static void createRealm() {
simpleToDnRealm = LdapSecurityRealmBuilder.builder()
.setDirContextSupplier(LdapTestSuite.dirContextFactory.create())
.identityMapping()
.setSearchDn("dc=elytron,dc=wildfly,dc=org")
.setRdnIdentifier("uid")
.build()
.userPasswordCredentialLoader()
.enablePersistence()
.build()
.otpCredentialLoader()
.setOtpAlgorithmAttribute("otpAlgorithm")
.setOtpHashAttribute("otpHash")
.setOtpSeedAttribute("otpSeed")
.setOtpSequenceAttribute("otpSequence")
.build()
.build();
}
@Test
public void testPlainUser() throws Exception {
performSimpleNameTest("plainUser", ClearPassword.ALGORITHM_CLEAR, "plainPassword".toCharArray());
}
@Test
public void testMd5User() throws Exception {
performSimpleNameTest("md5User", SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_MD5,
"md5Password".toCharArray());
}
@Test
public void testSmd5User() throws Exception {
performSimpleNameTest("smd5User", SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_MD5, "smd5Password".toCharArray());
}
@Test
public void testSha512User() throws Exception {
performSimpleNameTest("sha512User", SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_512, "sha512Password".toCharArray());
}
@Test
public void testSsha512User() throws Exception {
performSimpleNameTest("ssha512User", SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_512, "ssha512Password".toCharArray());
}
@Test
public void testCryptUser() throws Exception {
performSimpleNameTest("cryptUser", UnixDESCryptPassword.ALGORITHM_CRYPT_DES, "cryptIt".toCharArray());
}
@Test
public void testCryptUserLongPassword() throws Exception {
performSimpleNameTest("cryptUserLong", UnixDESCryptPassword.ALGORITHM_CRYPT_DES, "cryptPassword".toCharArray());
}
@Test
public void testBsdCryptUser() throws Exception {
performSimpleNameTest("bsdCryptUser", BSDUnixDESCryptPassword.ALGORITHM_BSD_CRYPT_DES, "cryptPassword".toCharArray());
}
@Test
public void testOneTimePasswordUser0() throws Exception {
SupportLevel support = simpleToDnRealm.getCredentialAcquireSupport(PasswordCredential.class, null);
assertEquals("Pre identity", SupportLevel.SUPPORTED, support);
RealmIdentity identity = simpleToDnRealm.getRealmIdentity(new NamePrincipal("userWithOtp"));
verifyPasswordSupport(identity, OneTimePassword.ALGORITHM_OTP_SHA1, SupportLevel.SUPPORTED);
OneTimePassword otp = identity.getCredential(PasswordCredential.class, OneTimePassword.ALGORITHM_OTP_SHA1).getPassword(OneTimePassword.class);
assertNotNull(otp);
assertEquals(1234, otp.getSequenceNumber());
Assert.assertArrayEquals(new byte[] { 'a', 'b', 'c', 'd' }, otp.getHash());
Assert.assertArrayEquals(new byte[] { 'e', 'f', 'g', 'h' }, otp.getSeed());
}
@Test
public void testOneTimePasswordUser1Update() throws Exception {
OneTimePasswordSpec spec = new OneTimePasswordSpec(new byte[] { 'i', 'j', 'k' }, new byte[] { 'l', 'm', 'n' }, 4321);
final PasswordFactory passwordFactory = PasswordFactory.getInstance("otp-sha1");
final OneTimePassword password = (OneTimePassword) passwordFactory.generatePassword(spec);
assertNotNull(password);
ModifiableRealmIdentity identity = (ModifiableRealmIdentity) simpleToDnRealm.getRealmIdentity(new NamePrincipal("userWithOtp"));
assertNotNull(identity);
assertEquals(SupportLevel.POSSIBLY_SUPPORTED, simpleToDnRealm.getCredentialAcquireSupport(PasswordCredential.class, OneTimePassword.ALGORITHM_OTP_SHA1));
assertEquals(SupportLevel.SUPPORTED, identity.getCredentialAcquireSupport(PasswordCredential.class, OneTimePassword.ALGORITHM_OTP_SHA1));
identity.setCredentials(Collections.singleton(new PasswordCredential(password)));
ModifiableRealmIdentity newIdentity = (ModifiableRealmIdentity) simpleToDnRealm.getRealmIdentity(new NamePrincipal("userWithOtp"));
assertNotNull(newIdentity);
verifyPasswordSupport(newIdentity, OneTimePassword.ALGORITHM_OTP_SHA1, SupportLevel.SUPPORTED);
OneTimePassword otp = newIdentity.getCredential(PasswordCredential.class, OneTimePassword.ALGORITHM_OTP_SHA1).getPassword(OneTimePassword.class);
assertNotNull(otp);
assertEquals(4321, otp.getSequenceNumber());
Assert.assertArrayEquals(new byte[] { 'i', 'j', 'k' }, otp.getHash());
Assert.assertArrayEquals(new byte[] { 'l', 'm', 'n' }, otp.getSeed());
}
@Test
public void testOneTimePasswordUser2SetCredentials() throws Exception {
OneTimePasswordSpec spec = new OneTimePasswordSpec(new byte[] { 'o', 'p', 'q' }, new byte[] { 'r', 's', 't' }, 65);
final PasswordFactory passwordFactory = PasswordFactory.getInstance("otp-sha1");
final OneTimePassword password = (OneTimePassword) passwordFactory.generatePassword(spec);
assertNotNull(password);
ModifiableRealmIdentity identity = (ModifiableRealmIdentity) simpleToDnRealm.getRealmIdentity(new NamePrincipal("userWithOtp"));
assertNotNull(identity);
identity.setCredentials(Collections.emptyList());
identity.setCredentials(Collections.emptyList()); // double clearing should not fail
identity.setCredentials(Collections.singleton(new PasswordCredential(password)));
ModifiableRealmIdentity newIdentity = (ModifiableRealmIdentity) simpleToDnRealm.getRealmIdentity(new NamePrincipal("userWithOtp"));
assertNotNull(newIdentity);
verifyPasswordSupport(newIdentity, OneTimePassword.ALGORITHM_OTP_SHA1, SupportLevel.SUPPORTED);
OneTimePassword otp = newIdentity.getCredential(PasswordCredential.class, OneTimePassword.ALGORITHM_OTP_SHA1).getPassword(OneTimePassword.class);
assertNotNull(otp);
assertEquals(65, otp.getSequenceNumber());
Assert.assertArrayEquals(new byte[] { 'o', 'p', 'q' }, otp.getHash());
Assert.assertArrayEquals(new byte[] { 'r', 's', 't' }, otp.getSeed());
}
@Test
public void testUserPasswordUserUpdate() throws Exception {
PasswordFactory factory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR);
ClearPassword password = (ClearPassword) factory.generatePassword(new ClearPasswordSpec("createdPassword".toCharArray()));
assertNotNull(password);
ModifiableRealmIdentity identity = (ModifiableRealmIdentity) simpleToDnRealm.getRealmIdentity(new NamePrincipal("userToChange"));
assertNotNull(identity);
assertEquals(SupportLevel.POSSIBLY_SUPPORTED, simpleToDnRealm.getCredentialAcquireSupport(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR));
assertEquals(SupportLevel.SUPPORTED, identity.getCredentialAcquireSupport(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR));
identity.setCredentials(Collections.singleton(new PasswordCredential(password)));
ModifiableRealmIdentity newIdentity = (ModifiableRealmIdentity) simpleToDnRealm.getRealmIdentity(new NamePrincipal("userToChange"));
assertNotNull(newIdentity);
verifyPasswordSupport(newIdentity, ClearPassword.ALGORITHM_CLEAR, SupportLevel.SUPPORTED);
ClearPassword password2 = newIdentity.getCredential(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR).getPassword(ClearPassword.class);
assertNotNull(password2);
Assert.assertEquals("createdPassword", new String(password2.getPassword()));
}
private void performSimpleNameTest(String simpleName, String algorithm, char[] password) throws NoSuchAlgorithmException, InvalidKeyException, RealmUnavailableException {
RealmIdentity realmIdentity = simpleToDnRealm.getRealmIdentity(new NamePrincipal(simpleName));
SupportLevel support = simpleToDnRealm.getCredentialAcquireSupport(PasswordCredential.class, algorithm);
assertEquals("Pre identity", SupportLevel.POSSIBLY_SUPPORTED, support);
verifyPasswordSupport(realmIdentity, algorithm, SupportLevel.SUPPORTED);
verifyPassword(realmIdentity, algorithm, password);
}
private void verifyPasswordSupport(RealmIdentity identity, final String algorithm, SupportLevel requiredSupport) throws RealmUnavailableException {
SupportLevel credentialSupport = identity.getCredentialAcquireSupport(PasswordCredential.class, algorithm);
assertEquals("Identity level support", requiredSupport, credentialSupport);
}
private void verifyPassword(RealmIdentity identity, String algorithm, char[] password) throws NoSuchAlgorithmException, InvalidKeyException, RealmUnavailableException {
Password loadedPassword = identity.getCredential(PasswordCredential.class).getPassword();
PasswordFactory factory = PasswordFactory.getInstance(algorithm);
final Password translated = factory.translate(loadedPassword);
assertTrue("Valid Password", factory.verify(translated, password));
assertFalse("Invalid Password", factory.verify(translated, "LetMeIn".toCharArray()));
}
}