@@ -46,16 +46,20 @@
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.wildfly.common.iteration.ByteIterator;
import org.wildfly.security.WildFlyElytronProvider;
import org.wildfly.security.password.PasswordFactory;
import org.wildfly.security.password.interfaces.BCryptPassword;
import org.wildfly.security.password.interfaces.BSDUnixDESCryptPassword;
import org.wildfly.security.password.spec.EncryptablePasswordSpec;
import org.wildfly.security.password.spec.IteratedSaltedPasswordAlgorithmSpec;
import org.wildfly.security.password.util.ModularCrypt;
import org.wildfly.security.permission.ElytronPermission;
import org.wildfly.test.security.common.AbstractElytronSetupTask;
import org.wildfly.test.security.common.elytron.ConfigurableElement;
import org.wildfly.test.security.common.elytron.ConstantRealmMapper;
import org.wildfly.test.security.common.elytron.JdbcSecurityRealm;
import org.wildfly.test.security.common.elytron.JdbcSecurityRealm.Encoding;
import org.wildfly.test.security.common.elytron.MappedRegexRealmMapper;
import org.wildfly.test.security.common.elytron.RegexPrincipalTransformer;
import org.wildfly.test.security.common.elytron.SimpleSecurityDomain;
@@ -123,6 +127,38 @@ public void testCombinedBCRYPTPassword_Success(@ArquillianResource URL webAppURL
assertEquals(JdbcTestServlet.RESPONSE_BODY, result);
}

@Test
@OperateOnDeployment(DEPLOYMENT)
public void testBCRYPTHexPassword_Success(@ArquillianResource URL webAppURL) throws Exception {
String result = Utils.makeCallWithBasicAuthn(convert(webAppURL, "userFive%40wildfly.org", "Violet", "Amber"), "userFive@BCRYPT_HEX", "passwordFive", SC_OK);

assertEquals(JdbcTestServlet.RESPONSE_BODY, result);
}

@Test
@OperateOnDeployment(DEPLOYMENT)
public void testCombinedBCRYPTHexPassword_Success(@ArquillianResource URL webAppURL) throws Exception {
String result = Utils.makeCallWithBasicAuthn(convert(webAppURL, "userSix%40wildfly.org", "Pink", "Gold"), "userSix@Combined", "passwordSix", SC_OK);

assertEquals(JdbcTestServlet.RESPONSE_BODY, result);
}

@Test
@OperateOnDeployment(DEPLOYMENT)
public void testModularCryptPassword_Success(@ArquillianResource URL webAppURL) throws Exception {
String result = Utils.makeCallWithBasicAuthn(convert(webAppURL, "userSeven%40wildfly.org", "Grey", "Indigo"), "userSeven@MODULAR_CRYPT", "passwordSeven", SC_OK);

assertEquals(JdbcTestServlet.RESPONSE_BODY, result);
}

@Test
@OperateOnDeployment(DEPLOYMENT)
public void testCombinedModularCryptPassword_Success(@ArquillianResource URL webAppURL) throws Exception {
String result = Utils.makeCallWithBasicAuthn(convert(webAppURL, "userEight%40wildfly.org", "Lilac", "Bergundy"), "userEight@Combined", "passwordEight", SC_OK);

assertEquals(JdbcTestServlet.RESPONSE_BODY, result);
}

private URL convert(URL original, final String email, final String... colours) throws Exception {
StringBuilder sb = new StringBuilder(original.toExternalForm())
.append(JdbcTestServlet.SERVLET_PATH.substring(1))
@@ -139,7 +175,7 @@ private URL convert(URL original, final String email, final String... colours) t

@Override
protected ConfigurableElement[] getConfigurableElements() {
ConfigurableElement[] elements = new ConfigurableElement[10];
ConfigurableElement[] elements = new ConfigurableElement[14];

// Clear Realm
elements[0] = JdbcSecurityRealm.builder("clear_realm")
@@ -165,8 +201,32 @@ private URL convert(URL original, final String email, final String... colours) t
.build();
// BCRYPT Realm Mapper
elements[3] = ConstantRealmMapper.newInstance("bcrypt_realm_mapper", "bcrypt_realm");
// BCRYPT Hex Realm
elements[4] = JdbcSecurityRealm.builder("bcrypt_hex_realm")
.withPrincipalQuery(DATASOURCE_NAME, "select hash, salt, iteration_count, email from bcrypt_hex_identities where name = ?")
.withPasswordMapper("bcrypt-mapper", null, 1, Encoding.HEX, 2, Encoding.HEX, 3)
.withAttributeMapper("email", 4)
.build()
.withPrincipalQuery(DATASOURCE_NAME, "select colour from colours where name = ?")
.withAttributeMapper("favourite-colours", 1)
.build()
.build();
// BCRYPT Hex Mapper
elements[5] = ConstantRealmMapper.newInstance("bcrypt_hex_realm_mapper", "bcrypt_hex_realm");
// Modular Crypt Realm
elements[6] = JdbcSecurityRealm.builder("modular_crypt_realm")
.withPrincipalQuery(DATASOURCE_NAME, "select password, email from modular_crypt_identities where name = ?")
.withPasswordMapper("modular-crypt-mapper", null, 1, -1, -1)
.withAttributeMapper("email", 2)
.build()
.withPrincipalQuery(DATASOURCE_NAME, "select colour from colours where name = ?")
.withAttributeMapper("favourite-colours", 1)
.build()
.build();
// Modular Crypt Mapper
elements[7] = ConstantRealmMapper.newInstance("modular_crypt_realm_mapper", "modular_crypt_realm");
// Combined Realm
elements[4] = JdbcSecurityRealm.builder("combined_realm")
elements[8] = JdbcSecurityRealm.builder("combined_realm")
.withPrincipalQuery(DATASOURCE_NAME, "select password, email from clear_identities where name = ?")
.withPasswordMapper("clear-password-mapper", null, 1, -1, -1)
.withAttributeMapper("email", 2)
@@ -175,37 +235,49 @@ private URL convert(URL original, final String email, final String... colours) t
.withPasswordMapper("bcrypt-mapper", null, 1, 2, 3)
.withAttributeMapper("email", 4)
.build()
.withPrincipalQuery(DATASOURCE_NAME, "select hash, salt, iteration_count, email from bcrypt_hex_identities where name = ?")
.withPasswordMapper("bcrypt-mapper", null, 1, Encoding.HEX, 2, Encoding.HEX, 3)
.withAttributeMapper("email", 4)
.build()
.withPrincipalQuery(DATASOURCE_NAME, "select password, email from modular_crypt_identities where name = ?")
.withPasswordMapper("modular-crypt-mapper", null, 1, -1, -1)
.withAttributeMapper("email", 2)
.build()
.withPrincipalQuery(DATASOURCE_NAME, "select colour from colours where name = ?")
.withAttributeMapper("favourite-colours", 1)
.build()
.build();
// Combined Realm Mapper
elements[5] = ConstantRealmMapper.newInstance("combined_realm_mapper", "combined_realm");
elements[9] = ConstantRealmMapper.newInstance("combined_realm_mapper", "combined_realm");
// RegEx RealmMapper
elements[6] = MappedRegexRealmMapper.builder("regex-mapper")
elements[10] = MappedRegexRealmMapper.builder("regex-mapper")
.withPattern(".+?@(.+)") // Reluctantly match all characters up to and including the first '@', all remaining characters into a single capturing group.
.withRealmMapping("Clear", "clear_realm")
.withRealmMapping("BCRYPT", "bcrypt_realm")
.withRealmMapping("BCRYPT_HEX", "bcrypt_hex_realm")
.withRealmMapping("MODULAR_CRYPT", "modular_crypt_realm")
.withRealmMapping("Combined", "combined_realm")
.build();
// Name Rewriter
elements[7] = RegexPrincipalTransformer.builder("realm-stripper")
elements[11] = RegexPrincipalTransformer.builder("realm-stripper")
.withPattern("@.+")
.withReplacement("")
.build();
// Security Domain
elements[8] = SimpleSecurityDomain.builder()
elements[12] = SimpleSecurityDomain.builder()
.withName("JdbcTestDomain")
.withPostRealmPrincipalTransformer("realm-stripper")
.withRealmMapper("regex-mapper")
.withPermissionMapper("default-permission-mapper")
.withRealms(SimpleSecurityDomain.SecurityDomainRealm.builder().withRealm("clear_realm").build(),
SimpleSecurityDomain.SecurityDomainRealm.builder().withRealm("bcrypt_realm").build(),
SimpleSecurityDomain.SecurityDomainRealm.builder().withRealm("bcrypt_hex_realm").build(),
SimpleSecurityDomain.SecurityDomainRealm.builder().withRealm("modular_crypt_realm").build(),
SimpleSecurityDomain.SecurityDomainRealm.builder().withRealm("combined_realm").build())
.build();

// Undertow Application Security Domain
elements[9] = UndertowApplicationSecurityDomain.builder()
elements[13] = UndertowApplicationSecurityDomain.builder()
.withName(DEPLOYMENT)
.withSecurityDomain("JdbcTestDomain")
.build();
@@ -242,12 +314,18 @@ public void setup(ManagementClient managementClient, String containerId) throws

executeUpdate(conn, "create table clear_identities (name VARCHAR PRIMARY KEY, password VARCHAR, email VARCHAR)");
executeUpdate(conn, "create table bcrypt_identities (name VARCHAR PRIMARY KEY, hash VARCHAR, salt VARCHAR, iteration_count INT, email VARCHAR)");
executeUpdate(conn, "create table bcrypt_hex_identities (name VARCHAR PRIMARY KEY, hash VARCHAR, salt VARCHAR, iteration_count INT, email VARCHAR)");
executeUpdate(conn, "create table colours (name VARCHAR, colour VARCHAR)");
executeUpdate(conn, "create table modular_crypt_identities (name VARCHAR PRIMARY KEY, password VARCHAR, email VARCHAR)");

addClearUser(conn, "userOne", "passwordOne", "userOne@wildfly.org", "Red", "Green");
addBCryptUser(conn, "userTwo", "passwordTwo", "userTwo@wildfly.org", "Black", "Blue");
addBCryptUser(conn, "userTwo", "passwordTwo", "userTwo@wildfly.org", false, "Black", "Blue");
addClearUser(conn, "userThree", "passwordThree", "userThree@wildfly.org", "Yellow", "Orange");
addBCryptUser(conn, "userFour", "passwordFour", "userFour@wildfly.org", "White", "Purple");
addBCryptUser(conn, "userFour", "passwordFour", "userFour@wildfly.org", false, "White", "Purple");
addBCryptUser(conn, "userFive", "passwordFive", "userFive@wildfly.org", true, "Violet", "Amber");
addBCryptUser(conn, "userSix", "passwordSix", "userSix@wildfly.org", true, "Pink", "Gold");
addModularCryptUser(conn, "userSeven", "passwordSeven", "userSeven@wildfly.org", "Grey", "Indigo");
addModularCryptUser(conn, "userEight", "passwordEight", "userEight@wildfly.org", "Lilac", "Bergundy");

conn.close();
}
@@ -257,7 +335,7 @@ private void addClearUser(final Connection conn, final String username, final St
addFavouriteColours(conn, username, colours);
}

private void addBCryptUser(final Connection conn, final String username, final String password, final String eMail, final String... colours) throws Exception {
private void addBCryptUser(final Connection conn, final String username, final String password, final String eMail, final boolean hexEncoded, final String... colours) throws Exception {
int iterationCount = 10;

byte[] salt = new byte[BCryptPassword.BCRYPT_SALT_SIZE];
@@ -273,10 +351,42 @@ private void addBCryptUser(final Connection conn, final String username, final S
byte[] hash = original.getHash();

Encoder encoder = Base64.getEncoder();
String encodedHash = encoder.encodeToString(hash);
String encodedSalt = encoder.encodeToString(salt);

executeUpdate(conn, String.format("insert into bcrypt_identities VALUES ('%s', '%s', '%s', %d, '%s')", username, encodedHash, encodedSalt, iterationCount, eMail));
final String tableName;
final String encodedHash;
final String encodedSalt;

if (hexEncoded) {
tableName = "bcrypt_hex_identities";
encodedHash = ByteIterator.ofBytes(hash).hexEncode().drainToString();
encodedSalt = ByteIterator.ofBytes(salt).hexEncode().drainToString();
} else {
tableName = "bcrypt_identities";
encodedHash = encoder.encodeToString(hash);
encodedSalt = encoder.encodeToString(salt);
}

executeUpdate(conn, String.format("insert into %s VALUES ('%s', '%s', '%s', %d, '%s')", tableName, username, encodedHash, encodedSalt, iterationCount, eMail));
addFavouriteColours(conn, username, colours);
}

private void addModularCryptUser(final Connection conn, final String username, final String password, final String eMail, final String... colours) throws Exception {
PasswordFactory passwordFactory = PasswordFactory.getInstance(BSDUnixDESCryptPassword.ALGORITHM_BSD_CRYPT_DES, PROVIDER);

int iterationCount = BSDUnixDESCryptPassword.DEFAULT_ITERATION_COUNT;

byte[] salt = new byte[BSDUnixDESCryptPassword.BSD_CRYPT_DES_SALT_SIZE];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);

IteratedSaltedPasswordAlgorithmSpec iteratedAlgorithmSpec = new IteratedSaltedPasswordAlgorithmSpec(iterationCount, salt);
EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(password.toCharArray(), iteratedAlgorithmSpec);

BSDUnixDESCryptPassword original = (BSDUnixDESCryptPassword) passwordFactory.generatePassword(encryptableSpec);

String encoded = ModularCrypt.encodeAsString(original);

executeUpdate(conn, String.format("insert into modular_crypt_identities VALUES ('%s', '%s', '%s')", username, encoded, eMail));
addFavouriteColours(conn, username, colours);
}

@@ -93,14 +93,27 @@ public static Builder builder(final String name) {
}

public PrincipalQueryBuilder withPasswordMapper(final String passwordType, final String algorithm, final int passwordIndex, final int saltIndex, final int iteractionCountIndex) {
return withPasswordMapper(passwordType, algorithm, passwordIndex, Encoding.BASE64, saltIndex, Encoding.BASE64, iteractionCountIndex);
}

public PrincipalQueryBuilder withPasswordMapper(final String passwordType, final String algorithm, final int passwordIndex, final Encoding passwordEncoding,
final int saltIndex, final Encoding saltEncoding, final int iteractionCountIndex) {
ModelNode passwordMapper = new ModelNode();
if (algorithm != null) {
passwordMapper.get("algorithm").set(algorithm);
}
passwordMapper.get("password-index").set(passwordIndex);
if (passwordEncoding == Encoding.HEX) {
passwordMapper.get("hash-encoding").set("hex");
}

if (saltIndex > 0) {
passwordMapper.get("salt-index").set(saltIndex);
if (saltEncoding == Encoding.HEX) {
passwordMapper.get("salt-encoding").set("hex");
}
}

if (iteractionCountIndex > 0) {
passwordMapper.get("iteration-count-index").set(iteractionCountIndex);
}
@@ -160,6 +173,9 @@ public SecurityRealm build() {

}

public enum Encoding {
BASE64, HEX;
}