/* * 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.password.impl; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_MD5; import static org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_1; import static org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_256; import static org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_384; import static org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_512; import static org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_MD5; import static org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_1; import static org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_256; import static org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_384; import static org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_512; import java.nio.charset.StandardCharsets; import java.security.Provider; import java.security.Security; import java.util.Arrays; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.wildfly.security.WildFlyElytronProvider; import org.wildfly.security.password.PasswordFactory; import org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword; import org.wildfly.security.password.spec.EncryptablePasswordSpec; import org.wildfly.security.password.spec.SaltedHashPasswordSpec; import org.wildfly.security.password.spec.SaltedPasswordAlgorithmSpec; import org.wildfly.security.util.Alphabet.Base64Alphabet; import org.wildfly.security.util.CodePointIterator; /** * Test case for the {@link SaltedSimpleDigestPassword} implementation. * * This test deliberately avoids testing storage representations and parsing capabilities as those are realm specific instead * this test focuses on the use of the Elytron APIs for the handling of this password type. * * For the purpose of testing a constant salt of 'salt' is used, along with a constant password of 'password'. * * @author <a href="mailto:darran.lofthouse@jboss.com">Darran Lofthouse</a> */ public class SaltedSimpleDigestPasswordTest { private static final byte[] salt = "salt".getBytes(StandardCharsets.UTF_8); private static final char[] password = "password".toCharArray(); private static final Provider provider = new WildFlyElytronProvider(); @BeforeClass public static void registerProvider() { Security.addProvider(provider); } @AfterClass public static void removeProvider() { Security.removeProvider(provider.getName()); } @Test public void testPasswordSaltMd5() throws Exception { performTest(ALGORITHM_PASSWORD_SALT_DIGEST_MD5, "swXK27O85U86pZxk/sAN6g==".toCharArray()); } @Test public void testPasswordSaltSha1() throws Exception { performTest(ALGORITHM_PASSWORD_SALT_DIGEST_SHA_1, "yI6cZwQadOA1e+/f+T+H3eCQQhQ".toCharArray()); } @Test public void testPasswordSaltSha256() throws Exception { performTest(ALGORITHM_PASSWORD_SALT_DIGEST_SHA_256, "eje4XIkY6sGakInA+loqtNzj+QUo3N7sEIsj3fNge5k".toCharArray()); } @Test public void testPasswordSaltSha384() throws Exception { performTest(ALGORITHM_PASSWORD_SALT_DIGEST_SHA_384, "JhBd8taJ7iy7c13rUwqdA0BiYlK+mbYtvmYKgUUH/Hnzlu9+YBTxYd973pJ5gvjP".toCharArray()); } @Test public void testPasswordSaltSha512() throws Exception { performTest(ALGORITHM_PASSWORD_SALT_DIGEST_SHA_512, "+mohhbPgqahe9B/7Z+88H7b3SYD46/lw5OcuNT7ZU31ZMIPCAd/W5D4cinqsK8jbsRnH37fUuPExEROVvXDpfw".toCharArray()); } @Test public void testSaltPasswordMd5() throws Exception { performTest(ALGORITHM_SALT_PASSWORD_DIGEST_MD5, "Z6Hgm7H4P1AH3BGcFNZjqg==".toCharArray()); } @Test public void testSaltPasswordSha1() throws Exception { performTest(ALGORITHM_SALT_PASSWORD_DIGEST_SHA_1, "WbPo1jfPl+2+I4TPWct0U9/jB4k".toCharArray()); } @Test public void testSaltPasswordSha256() throws Exception { performTest(ALGORITHM_SALT_PASSWORD_DIGEST_SHA_256, "E2Ab2k6njlWge5iGbSvmvgdE44ZvE8AMgRyrYIoo8yI".toCharArray()); } @Test public void testSaltPasswordSha384() throws Exception { performTest(ALGORITHM_SALT_PASSWORD_DIGEST_SHA_384, "9L2smGDAzupp+ynvvOJK3cpc8fgIkl2UM7ZoUoKQ1dLJCA8yNCF1tRJIlWhNuLpP".toCharArray()); } @Test public void testSaltPasswordSha512() throws Exception { performTest(ALGORITHM_SALT_PASSWORD_DIGEST_SHA_512, "KQjSwo38BHdB/FkKAm/63iN6srp+EmbwEP5JveVItZh6U0qGZVoNF/M2WI5UDNZvZyNLFSu7ZFtLuFdYoTJdZA".toCharArray()); } /** * Perform a test for the specified algorithm with the pre-prepared digest for that algorithm. * * @param algorithmName the algorithm to use to perform a test. * @param base64Digest the Base64 representation of the expected digest for this algorithm. */ private void performTest(final String algorithmName, final char[] base64Digest) throws Exception { byte[] preDigested = CodePointIterator.ofChars(base64Digest, 0).base64Decode(Base64Alphabet.STANDARD, false).drain(); PasswordFactory pf = PasswordFactory.getInstance(algorithmName); // Encryptable Spec -> Password SaltedPasswordAlgorithmSpec spac = new SaltedPasswordAlgorithmSpec(salt); EncryptablePasswordSpec eps = new EncryptablePasswordSpec(password, spac); SaltedSimpleDigestPassword tsdp = (SaltedSimpleDigestPassword) pf.generatePassword(eps); validatePassword(tsdp, preDigested, pf); assertTrue("Convertable to key spec", pf.convertibleToKeySpec(tsdp, SaltedHashPasswordSpec.class)); SaltedHashPasswordSpec tsdps = pf.getKeySpec(tsdp, SaltedHashPasswordSpec.class); assertTrue("Salt Correctly Passed", Arrays.equals(salt, tsdps.getSalt())); assertTrue("Digest Correctly Generated", Arrays.equals(preDigested, tsdps.getHash())); // Digest into Spec -> Password tsdps = new SaltedHashPasswordSpec(preDigested, salt); tsdp = (SaltedSimpleDigestPassword) pf.generatePassword(tsdps); validatePassword(tsdp, preDigested, pf); // Custom SaltedSimpleDigestPassword implementation. TestPasswordImpl tpi = new TestPasswordImpl(algorithmName, salt, preDigested); tsdp = (SaltedSimpleDigestPassword) pf.translate(tpi); validatePassword(tsdp, preDigested, pf); } private void validatePassword(SaltedSimpleDigestPassword tsdp, byte[] preDigested, PasswordFactory pf) throws Exception { assertTrue("Salt Correctly Passed", Arrays.equals(salt, tsdp.getSalt())); assertTrue("Digest Correctly Generated", Arrays.equals(preDigested, tsdp.getDigest())); assertTrue("Password Validation", pf.verify(tsdp, password)); assertFalse("Bad Password Rejection", pf.verify(tsdp, "bad".toCharArray())); } private class TestPasswordImpl implements SaltedSimpleDigestPassword { private static final long serialVersionUID = 1L; private final String algorithm; private final byte[] salt; private final byte[] digest; private TestPasswordImpl(final String algorithm, final byte[] salt, final byte[] digest) { this.algorithm = algorithm; this.salt = salt; this.digest = digest; } @Override public String getAlgorithm() { return algorithm; } @Override public String getFormat() { return null; } @Override public byte[] getEncoded() { return new byte[0]; } @Override public byte[] getDigest() { return digest; } @Override public byte[] getSalt() { return salt; } public TestPasswordImpl clone() { return this; } } }