/* * JBoss, Home of Professional Open Source * * Copyright 2013 Red Hat, Inc. and/or its affiliates. * * 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.auth.realm.jdbc; import static org.junit.Assert.assertArrayEquals; 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 static org.wildfly.security.password.interfaces.BCryptPassword.BCRYPT_SALT_SIZE; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import org.junit.ClassRule; import org.junit.Test; import org.wildfly.security.auth.principal.NamePrincipal; import org.wildfly.security.auth.realm.jdbc.mapper.PasswordKeyMapper; import org.wildfly.security.auth.server.RealmIdentity; import org.wildfly.security.auth.SupportLevel; import org.wildfly.security.credential.PasswordCredential; import org.wildfly.security.evidence.PasswordGuessEvidence; import org.wildfly.security.password.PasswordFactory; import org.wildfly.security.password.interfaces.BCryptPassword; import org.wildfly.security.password.interfaces.ClearPassword; import org.wildfly.security.password.interfaces.SaltedSimpleDigestPassword; import org.wildfly.security.password.interfaces.ScramDigestPassword; import org.wildfly.security.password.interfaces.SimpleDigestPassword; import org.wildfly.security.password.spec.ClearPasswordSpec; import org.wildfly.security.password.spec.EncryptablePasswordSpec; import org.wildfly.security.password.spec.IteratedSaltedHashPasswordSpec; import org.wildfly.security.password.spec.IteratedSaltedPasswordAlgorithmSpec; import org.wildfly.security.password.spec.SaltedPasswordAlgorithmSpec; import org.wildfly.security.password.util.ModularCrypt; import org.wildfly.security.password.util.PasswordUtil; /** * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> */ public class PasswordSupportTest { @ClassRule public static final DataSourceRule dataSourceRule = new DataSourceRule(); @Test public void testVerifyAndObtainClearPasswordCredential() throws Exception { String userName = "john"; String userPassword = "abcd1234"; createClearPasswordTable(userName, userPassword); PasswordKeyMapper passwordKeyMapper = PasswordKeyMapper.builder() .setDefaultAlgorithm(ClearPassword.ALGORITHM_CLEAR) .setHashColumn(1) .build(); JdbcSecurityRealm securityRealm = JdbcSecurityRealm.builder() .principalQuery("SELECT password FROM user_clear_password WHERE name = ?") .withMapper(passwordKeyMapper) .from(dataSourceRule.getDataSource()) .build(); assertTrue(securityRealm.getCredentialAcquireSupport(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR).mayBeSupported()); RealmIdentity realmIdentity = securityRealm.getRealmIdentity(new NamePrincipal(userName)); assertTrue(realmIdentity.getCredentialAcquireSupport(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR).isDefinitelySupported()); PasswordFactory passwordFactory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR); ClearPassword password = (ClearPassword) passwordFactory.generatePassword(new ClearPasswordSpec(userPassword.toCharArray())); assertTrue(realmIdentity.verifyEvidence(new PasswordGuessEvidence(userPassword.toCharArray()))); PasswordGuessEvidence invalidPassword = new PasswordGuessEvidence("badpasswd".toCharArray()); assertFalse(realmIdentity.verifyEvidence(invalidPassword)); ClearPassword storedPassword = realmIdentity.getCredential(PasswordCredential.class, ClearPassword.ALGORITHM_CLEAR).getPassword(ClearPassword.class); assertNotNull(storedPassword); assertArrayEquals(password.getPassword(), storedPassword.getPassword()); } @Test public void testVerifyAndObtainBCryptPasswordCredentialUsingModularCrypt() throws Exception { String userName = "john"; String userPassword = "bcrypt_abcd1234"; String cryptString = createBcryptPasswordTable(userName, userPassword); PasswordKeyMapper passwordKeyMapper = PasswordKeyMapper.builder() .setDefaultAlgorithm(BCryptPassword.ALGORITHM_BCRYPT) .setHashColumn(1) .build(); JdbcSecurityRealm securityRealm = JdbcSecurityRealm.builder() .principalQuery("SELECT password FROM user_bcrypt_password where name = ?") .withMapper(passwordKeyMapper) .from(dataSourceRule.getDataSource()) .build(); assertEquals(SupportLevel.POSSIBLY_SUPPORTED, securityRealm.getCredentialAcquireSupport(PasswordCredential.class, BCryptPassword.ALGORITHM_BCRYPT)); RealmIdentity realmIdentity = securityRealm.getRealmIdentity(new NamePrincipal(userName)); assertEquals(SupportLevel.SUPPORTED, realmIdentity.getCredentialAcquireSupport(PasswordCredential.class, BCryptPassword.ALGORITHM_BCRYPT)); assertTrue(realmIdentity.verifyEvidence(new PasswordGuessEvidence(userPassword.toCharArray()))); assertFalse(realmIdentity.verifyEvidence(new PasswordGuessEvidence("invalid".toCharArray()))); BCryptPassword storedPassword = realmIdentity.getCredential(PasswordCredential.class, BCryptPassword.ALGORITHM_BCRYPT).getPassword(BCryptPassword.class); assertNotNull(storedPassword); // use the new password to obtain a spec and then check if the spec yields the same crypt string. assertEquals(cryptString, ModularCrypt.encodeAsString(storedPassword)); } @Test public void testVerifyAndObtainBCryptPasswordCredential() throws Exception { String userName = "john"; String userPassword = "bcrypt_abcd1234"; byte[] salt = PasswordUtil.generateRandomSalt(BCRYPT_SALT_SIZE); int iterationCount = 10; createBcryptPasswordTable(userName, userPassword, salt, iterationCount); PasswordKeyMapper passwordKeyMapper = PasswordKeyMapper.builder() .setDefaultAlgorithm(BCryptPassword.ALGORITHM_BCRYPT) .setHashColumn(1) .setSaltColumn(2) .setIterationCountColumn(3) .build(); JdbcSecurityRealm securityRealm = JdbcSecurityRealm.builder() .principalQuery("SELECT password, salt, iterationCount FROM user_bcrypt_password where name = ?") .withMapper(passwordKeyMapper) .from(dataSourceRule.getDataSource()) .build(); assertEquals(SupportLevel.POSSIBLY_SUPPORTED, securityRealm.getCredentialAcquireSupport(PasswordCredential.class, BCryptPassword.ALGORITHM_BCRYPT)); RealmIdentity realmIdentity = securityRealm.getRealmIdentity(new NamePrincipal(userName)); assertEquals(SupportLevel.SUPPORTED, realmIdentity.getCredentialAcquireSupport(PasswordCredential.class, BCryptPassword.ALGORITHM_BCRYPT)); assertTrue(realmIdentity.verifyEvidence(new PasswordGuessEvidence(userPassword.toCharArray()))); assertFalse(realmIdentity.verifyEvidence(new PasswordGuessEvidence("invalid".toCharArray()))); BCryptPassword storedPassword = realmIdentity.getCredential(PasswordCredential.class, BCryptPassword.ALGORITHM_BCRYPT).getPassword(BCryptPassword.class); assertNotNull(storedPassword); } @Test public void testVerifyAndObtainSaltedDigestPasswordCredential() throws Exception { assertVerifyAndObtainSaltedDigestPasswordCredential(SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_512); assertVerifyAndObtainSaltedDigestPasswordCredential(SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_1); assertVerifyAndObtainSaltedDigestPasswordCredential(SaltedSimpleDigestPassword.ALGORITHM_SALT_PASSWORD_DIGEST_SHA_384); assertVerifyAndObtainSaltedDigestPasswordCredential(SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_512); assertVerifyAndObtainSaltedDigestPasswordCredential(SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_1); assertVerifyAndObtainSaltedDigestPasswordCredential(SaltedSimpleDigestPassword.ALGORITHM_PASSWORD_SALT_DIGEST_SHA_384); } public void assertVerifyAndObtainSaltedDigestPasswordCredential(String algorithm) throws Exception { String userName = "john"; String userPassword = "salted_digest_abcd1234"; SaltedSimpleDigestPassword password = createSaltedDigestPasswordTable(algorithm, userName, userPassword); PasswordKeyMapper passwordKeyMapper = PasswordKeyMapper.builder() .setDefaultAlgorithm(algorithm) .setHashColumn(1) .setSaltColumn(2) .build(); JdbcSecurityRealm securityRealm = JdbcSecurityRealm.builder() .principalQuery("SELECT digest, salt FROM user_salted_digest_password where name = ?") .withMapper(passwordKeyMapper) .from(dataSourceRule.getDataSource()) .build(); assertEquals(SupportLevel.POSSIBLY_SUPPORTED, securityRealm.getCredentialAcquireSupport(PasswordCredential.class, algorithm)); RealmIdentity realmIdentity = securityRealm.getRealmIdentity(new NamePrincipal(userName)); assertEquals(SupportLevel.SUPPORTED, realmIdentity.getCredentialAcquireSupport(PasswordCredential.class, algorithm)); assertTrue(realmIdentity.verifyEvidence(new PasswordGuessEvidence(userPassword.toCharArray()))); SaltedSimpleDigestPassword storedPassword = realmIdentity.getCredential(PasswordCredential.class, algorithm).getPassword(SaltedSimpleDigestPassword.class); assertNotNull(storedPassword); assertArrayEquals(password.getDigest(), storedPassword.getDigest()); assertArrayEquals(password.getSalt(), storedPassword.getSalt()); } @Test public void testVerifySimpleDigestPasswordCredential() throws Exception { assertVerifyAndObtainSimpleDigestPasswordSHA512Credential(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_512); assertVerifyAndObtainSimpleDigestPasswordSHA512Credential(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_MD5); assertVerifyAndObtainSimpleDigestPasswordSHA512Credential(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_MD2); } public void assertVerifyAndObtainSimpleDigestPasswordSHA512Credential(String algorithm) throws Exception { String userName = "john"; String userPassword = "simple_digest_abcd1234"; SimpleDigestPassword password = createSimpleDigestPasswordTable(algorithm, userName, userPassword); PasswordKeyMapper passwordKeyMapper = PasswordKeyMapper.builder() .setDefaultAlgorithm(algorithm) .setHashColumn(1) .build(); JdbcSecurityRealm securityRealm = JdbcSecurityRealm.builder() .principalQuery("SELECT digest FROM user_simple_digest_password where name = ?") .withMapper(passwordKeyMapper) .from(dataSourceRule.getDataSource()) .build(); assertEquals(SupportLevel.POSSIBLY_SUPPORTED, securityRealm.getCredentialAcquireSupport(PasswordCredential.class, algorithm)); RealmIdentity realmIdentity = securityRealm.getRealmIdentity(new NamePrincipal(userName)); assertEquals(SupportLevel.SUPPORTED, realmIdentity.getCredentialAcquireSupport(PasswordCredential.class, algorithm)); assertTrue(realmIdentity.verifyEvidence(new PasswordGuessEvidence(userPassword.toCharArray()))); SimpleDigestPassword storedPassword = realmIdentity.getCredential(PasswordCredential.class, algorithm).getPassword(SimpleDigestPassword.class); assertNotNull(storedPassword); assertArrayEquals(password.getDigest(), storedPassword.getDigest()); } @Test public void testVerifyAndObtainScramDigestPasswordCredential() throws Exception { String userName = "john"; String userPassword = "scram_digest_abcd1234"; IteratedSaltedHashPasswordSpec passwordSpec = createScramDigestPasswordTable(userName, userPassword); PasswordKeyMapper passwordKeyMapper = PasswordKeyMapper.builder() .setDefaultAlgorithm(ScramDigestPassword.ALGORITHM_SCRAM_SHA_256) .setHashColumn(1) .setSaltColumn(2) .setIterationCountColumn(3) .build(); JdbcSecurityRealm securityRealm = JdbcSecurityRealm.builder() .principalQuery("SELECT digest, salt, iterationCount FROM user_scram_digest_password where name = ?") .withMapper(passwordKeyMapper) .from(dataSourceRule.getDataSource()) .build(); assertEquals(SupportLevel.POSSIBLY_SUPPORTED, securityRealm.getCredentialAcquireSupport(PasswordCredential.class, ScramDigestPassword.ALGORITHM_SCRAM_SHA_256)); RealmIdentity realmIdentity = securityRealm.getRealmIdentity(new NamePrincipal(userName)); assertEquals(SupportLevel.SUPPORTED, realmIdentity.getCredentialAcquireSupport(PasswordCredential.class, ScramDigestPassword.ALGORITHM_SCRAM_SHA_256)); assertTrue(realmIdentity.verifyEvidence(new PasswordGuessEvidence(userPassword.toCharArray()))); ScramDigestPassword storedPassword = realmIdentity.getCredential(PasswordCredential.class, ScramDigestPassword.ALGORITHM_SCRAM_SHA_256).getPassword(ScramDigestPassword.class); assertNotNull(storedPassword); assertArrayEquals(passwordSpec.getHash(), storedPassword.getDigest()); assertArrayEquals(passwordSpec.getSalt(), storedPassword.getSalt()); assertEquals(passwordSpec.getIterationCount(), storedPassword.getIterationCount()); } private SaltedSimpleDigestPassword createSaltedDigestPasswordTable(String algorithm, String userName, String userPassword) throws Exception { try ( Connection connection = dataSourceRule.getDataSource().getConnection(); Statement statement = connection.createStatement(); ) { statement.executeUpdate("DROP TABLE IF EXISTS user_salted_digest_password"); statement.executeUpdate("CREATE TABLE user_salted_digest_password ( id INTEGER IDENTITY, name VARCHAR(100), digest OTHER, salt OTHER)"); } try ( Connection connection = dataSourceRule.getDataSource().getConnection(); PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO user_salted_digest_password (name, digest, salt) VALUES (?, ?, ?)"); ) { byte[] salt = PasswordUtil.generateRandomSalt(BCRYPT_SALT_SIZE); SaltedPasswordAlgorithmSpec spac = new SaltedPasswordAlgorithmSpec(salt); EncryptablePasswordSpec eps = new EncryptablePasswordSpec(userPassword.toCharArray(), spac); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); SaltedSimpleDigestPassword tsdp = (SaltedSimpleDigestPassword) passwordFactory.generatePassword(eps); preparedStatement.setString(1, userName); preparedStatement.setBytes(2, tsdp.getDigest()); preparedStatement.setBytes(3, tsdp.getSalt()); preparedStatement.execute(); return tsdp; } } private SimpleDigestPassword createSimpleDigestPasswordTable(String algorithm, String userName, String userPassword) throws Exception { try ( Connection connection = dataSourceRule.getDataSource().getConnection(); Statement statement = connection.createStatement(); ) { statement.executeUpdate("DROP TABLE IF EXISTS user_simple_digest_password"); statement.executeUpdate("CREATE TABLE user_simple_digest_password ( id INTEGER IDENTITY, name VARCHAR(100), digest OTHER)"); } try ( Connection connection = dataSourceRule.getDataSource().getConnection(); PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO user_simple_digest_password (name, digest) VALUES (?, ?)"); ) { EncryptablePasswordSpec eps = new EncryptablePasswordSpec(userPassword.toCharArray(), null); PasswordFactory passwordFactory = PasswordFactory.getInstance(algorithm); SimpleDigestPassword tsdp = (SimpleDigestPassword) passwordFactory.generatePassword(eps); preparedStatement.setString(1, userName); preparedStatement.setBytes(2, tsdp.getDigest()); preparedStatement.execute(); return tsdp; } } private IteratedSaltedHashPasswordSpec createScramDigestPasswordTable(String userName, String userPassword) throws Exception { try ( Connection connection = dataSourceRule.getDataSource().getConnection(); Statement statement = connection.createStatement(); ) { statement.executeUpdate("DROP TABLE IF EXISTS user_scram_digest_password"); statement.executeUpdate("CREATE TABLE user_scram_digest_password ( id INTEGER IDENTITY, name VARCHAR(100), digest OTHER, salt OTHER, iterationCount INTEGER)"); } try ( Connection connection = dataSourceRule.getDataSource().getConnection(); PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO user_scram_digest_password (name, digest, salt, iterationCount) VALUES (?, ?, ?, ?)"); ) { byte[] salt = PasswordUtil.generateRandomSalt(BCRYPT_SALT_SIZE); PasswordFactory factory = PasswordFactory.getInstance(ScramDigestPassword.ALGORITHM_SCRAM_SHA_256); IteratedSaltedPasswordAlgorithmSpec algoSpec = new IteratedSaltedPasswordAlgorithmSpec(4096, salt); EncryptablePasswordSpec encSpec = new EncryptablePasswordSpec(userPassword.toCharArray(), algoSpec); ScramDigestPassword scramPassword = (ScramDigestPassword) factory.generatePassword(encSpec); IteratedSaltedHashPasswordSpec keySpec = factory.getKeySpec(scramPassword, IteratedSaltedHashPasswordSpec.class); preparedStatement.setString(1, userName); preparedStatement.setBytes(2, keySpec.getHash()); preparedStatement.setBytes(3, keySpec.getSalt()); preparedStatement.setInt(4, keySpec.getIterationCount()); preparedStatement.execute(); return keySpec; } } private String createBcryptPasswordTable(String userName, String userPassword) throws Exception { try ( Connection connection = dataSourceRule.getDataSource().getConnection(); Statement statement = connection.createStatement(); ) { statement.executeUpdate("DROP TABLE IF EXISTS user_bcrypt_password"); statement.executeUpdate("CREATE TABLE user_bcrypt_password ( id INTEGER IDENTITY, name VARCHAR(100), password VARCHAR(100))"); } try ( Connection connection = dataSourceRule.getDataSource().getConnection(); PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO user_bcrypt_password (name, password) VALUES (?, ?)"); ) { byte[] salt = PasswordUtil.generateRandomSalt(BCRYPT_SALT_SIZE); PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT); BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( new EncryptablePasswordSpec(userPassword.toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(10, salt)) ); String cryptString = ModularCrypt.encodeAsString(bCryptPassword); preparedStatement.setString(1, userName); preparedStatement.setString(2, cryptString); preparedStatement.execute(); return cryptString; } } private void createBcryptPasswordTable(String userName, String userPassword, byte[] salt, int iterationCount) throws Exception { try ( Connection connection = dataSourceRule.getDataSource().getConnection(); Statement statement = connection.createStatement(); ) { statement.executeUpdate("DROP TABLE IF EXISTS user_bcrypt_password"); statement.executeUpdate("CREATE TABLE user_bcrypt_password ( id INTEGER IDENTITY, name VARCHAR(100), password OTHER, salt OTHER, iterationCount INTEGER)"); } try ( Connection connection = dataSourceRule.getDataSource().getConnection(); PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO user_bcrypt_password (name, password, salt, iterationCount) VALUES (?, ?, ?, ?)"); ) { PasswordFactory passwordFactory = PasswordFactory.getInstance(BCryptPassword.ALGORITHM_BCRYPT); BCryptPassword bCryptPassword = (BCryptPassword) passwordFactory.generatePassword( new EncryptablePasswordSpec(userPassword.toCharArray(), new IteratedSaltedPasswordAlgorithmSpec(iterationCount, salt)) ); preparedStatement.setString(1, userName); preparedStatement.setBytes(2, bCryptPassword.getHash()); preparedStatement.setBytes(3, bCryptPassword.getSalt()); preparedStatement.setInt(4, bCryptPassword.getIterationCount()); preparedStatement.execute(); } } private void createClearPasswordTable(String userName, String password) throws Exception { createClearPasswordTable(); insertUserWithClearPassword(userName, password); } private void insertUserWithClearPassword(String userName, String password) throws SQLException { try ( Connection connection = dataSourceRule.getDataSource().getConnection(); Statement statement = connection.createStatement(); ) { statement.executeUpdate("INSERT INTO user_clear_password (name, password) VALUES ('" + userName + "','" + password + "')"); } } private void createClearPasswordTable() throws Exception { try ( Connection connection = dataSourceRule.getDataSource().getConnection(); Statement statement = connection.createStatement(); ) { statement.executeUpdate("DROP TABLE IF EXISTS user_clear_password"); statement.executeUpdate("CREATE TABLE user_clear_password ( id INTEGER IDENTITY, name VARCHAR(100), password VARCHAR(50))"); } } }