forked from opensearch-project/security
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for PBKDF2 for password hashing & add support for configu…
…ring BCrypt and PBKDF2 (opensearch-project#4524) Signed-off-by: Dan Cecoi <[email protected]> Co-authored-by: Dan Cecoi <[email protected]>
- eavluate.ssl.exception.handler
- (opensearch-project/security#4524)
- gradle.8.10
- (opensearch-project/security#4524)
- gradle.8.11.1
- (opensearch-project/security#4524)
- gradle.8.12
- (opensearch-project/security#4524)
- gradle.8.9
- (opensearch-project/security#4524)
- issue-4805
- (opensearch-project/security#4524)
- issue-4881
- (opensearch-project/security#4524)
- update.gradle.8.11
- (opensearch-project/security#4524)
1 parent
be92bb6
commit 8d29b11
Showing
31 changed files
with
1,715 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
src/integrationTest/java/org/opensearch/security/hash/BCryptCustomConfigHashingTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.hash; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.apache.http.HttpStatus; | ||
import org.awaitility.Awaitility; | ||
import org.junit.BeforeClass; | ||
import org.junit.Test; | ||
|
||
import org.opensearch.security.support.ConfigConstants; | ||
import org.opensearch.test.framework.TestSecurityConfig; | ||
import org.opensearch.test.framework.cluster.ClusterManager; | ||
import org.opensearch.test.framework.cluster.LocalCluster; | ||
import org.opensearch.test.framework.cluster.TestRestClient; | ||
|
||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; | ||
import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; | ||
|
||
public class BCryptCustomConfigHashingTests extends HashingTests { | ||
|
||
private static LocalCluster cluster; | ||
|
||
private static String minor; | ||
|
||
private static int rounds; | ||
|
||
@BeforeClass | ||
public static void startCluster() { | ||
minor = randomFrom(List.of("A", "B", "Y")); | ||
rounds = randomIntBetween(4, 10); | ||
|
||
TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS) | ||
.hash(generateBCryptHash("secret", minor, rounds)); | ||
cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) | ||
.authc(AUTHC_HTTPBASIC_INTERNAL) | ||
.users(ADMIN_USER) | ||
.anonymousAuth(false) | ||
.nodeSettings( | ||
Map.of( | ||
ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, | ||
List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName()), | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, | ||
ConfigConstants.BCRYPT, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_MINOR, | ||
minor, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS, | ||
rounds | ||
) | ||
) | ||
.build(); | ||
cluster.before(); | ||
|
||
try (TestRestClient client = cluster.getRestClient(ADMIN_USER.getName(), "secret")) { | ||
Awaitility.await() | ||
.alias("Load default configuration") | ||
.until(() -> client.securityHealth().getTextFromJsonBody("/status"), equalTo("UP")); | ||
} | ||
} | ||
|
||
@Test | ||
public void shouldAuthenticateWithCorrectPassword() { | ||
String hash = generateBCryptHash(PASSWORD, minor, rounds); | ||
createUserWithHashedPassword(cluster, "user_2", hash); | ||
testPasswordAuth(cluster, "user_2", PASSWORD, HttpStatus.SC_OK); | ||
|
||
createUserWithPlainTextPassword(cluster, "user_3", PASSWORD); | ||
testPasswordAuth(cluster, "user_3", PASSWORD, HttpStatus.SC_OK); | ||
} | ||
|
||
@Test | ||
public void shouldNotAuthenticateWithIncorrectPassword() { | ||
String hash = generateBCryptHash(PASSWORD, minor, rounds); | ||
createUserWithHashedPassword(cluster, "user_4", hash); | ||
testPasswordAuth(cluster, "user_4", "wrong_password", HttpStatus.SC_UNAUTHORIZED); | ||
|
||
createUserWithPlainTextPassword(cluster, "user_5", PASSWORD); | ||
testPasswordAuth(cluster, "user_5", "wrong_password", HttpStatus.SC_UNAUTHORIZED); | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
src/integrationTest/java/org/opensearch/security/hash/BCryptDefaultConfigHashingTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.hash; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.apache.http.HttpStatus; | ||
import org.junit.ClassRule; | ||
import org.junit.Test; | ||
|
||
import org.opensearch.security.support.ConfigConstants; | ||
import org.opensearch.test.framework.TestSecurityConfig; | ||
import org.opensearch.test.framework.cluster.ClusterManager; | ||
import org.opensearch.test.framework.cluster.LocalCluster; | ||
|
||
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; | ||
import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; | ||
|
||
public class BCryptDefaultConfigHashingTests extends HashingTests { | ||
|
||
private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); | ||
|
||
@ClassRule | ||
public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) | ||
.authc(AUTHC_HTTPBASIC_INTERNAL) | ||
.users(ADMIN_USER) | ||
.anonymousAuth(false) | ||
.nodeSettings( | ||
Map.of(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName())) | ||
) | ||
.build(); | ||
|
||
@Test | ||
public void shouldAuthenticateWithCorrectPassword() { | ||
String hash = generateBCryptHash( | ||
PASSWORD, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_MINOR_DEFAULT, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS_DEFAULT | ||
); | ||
createUserWithHashedPassword(cluster, "user_2", hash); | ||
testPasswordAuth(cluster, "user_2", PASSWORD, HttpStatus.SC_OK); | ||
|
||
createUserWithPlainTextPassword(cluster, "user_3", PASSWORD); | ||
testPasswordAuth(cluster, "user_3", PASSWORD, HttpStatus.SC_OK); | ||
} | ||
|
||
@Test | ||
public void shouldNotAuthenticateWithIncorrectPassword() { | ||
String hash = generateBCryptHash( | ||
PASSWORD, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_MINOR_DEFAULT, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS_DEFAULT | ||
); | ||
createUserWithHashedPassword(cluster, "user_4", hash); | ||
testPasswordAuth(cluster, "user_4", "wrong_password", HttpStatus.SC_UNAUTHORIZED); | ||
|
||
createUserWithPlainTextPassword(cluster, "user_5", PASSWORD); | ||
testPasswordAuth(cluster, "user_5", "wrong_password", HttpStatus.SC_UNAUTHORIZED); | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
src/integrationTest/java/org/opensearch/security/hash/HashingTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.hash; | ||
|
||
import java.nio.CharBuffer; | ||
|
||
import com.carrotsearch.randomizedtesting.RandomizedTest; | ||
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; | ||
import org.apache.http.HttpStatus; | ||
import org.junit.runner.RunWith; | ||
|
||
import org.opensearch.test.framework.TestSecurityConfig; | ||
import org.opensearch.test.framework.cluster.LocalCluster; | ||
import org.opensearch.test.framework.cluster.TestRestClient; | ||
|
||
import com.password4j.BcryptFunction; | ||
import com.password4j.CompressedPBKDF2Function; | ||
import com.password4j.Password; | ||
import com.password4j.types.Bcrypt; | ||
|
||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; | ||
|
||
@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class) | ||
@ThreadLeakScope(ThreadLeakScope.Scope.NONE) | ||
public class HashingTests extends RandomizedTest { | ||
|
||
private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS); | ||
|
||
static final String PASSWORD = "top$ecret1234!"; | ||
|
||
public void createUserWithPlainTextPassword(LocalCluster cluster, String username, String password) { | ||
try (TestRestClient client = cluster.getRestClient(ADMIN_USER)) { | ||
TestRestClient.HttpResponse httpResponse = client.putJson( | ||
"_plugins/_security/api/internalusers/" + username, | ||
String.format("{\"password\": \"%s\",\"opendistro_security_roles\": []}", password) | ||
); | ||
assertThat(httpResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); | ||
} | ||
} | ||
|
||
public void createUserWithHashedPassword(LocalCluster cluster, String username, String hashedPassword) { | ||
try (TestRestClient client = cluster.getRestClient(ADMIN_USER)) { | ||
TestRestClient.HttpResponse httpResponse = client.putJson( | ||
"_plugins/_security/api/internalusers/" + username, | ||
String.format("{\"hash\": \"%s\",\"opendistro_security_roles\": []}", hashedPassword) | ||
); | ||
assertThat(httpResponse.getStatusCode(), equalTo(HttpStatus.SC_CREATED)); | ||
} | ||
} | ||
|
||
public void testPasswordAuth(LocalCluster cluster, String username, String password, int expectedStatusCode) { | ||
try (TestRestClient client = cluster.getRestClient(username, password)) { | ||
TestRestClient.HttpResponse response = client.getAuthInfo(); | ||
response.assertStatusCode(expectedStatusCode); | ||
} | ||
} | ||
|
||
public static String generateBCryptHash(String password, String minor, int rounds) { | ||
return Password.hash(CharBuffer.wrap(password.toCharArray())) | ||
.with(BcryptFunction.getInstance(Bcrypt.valueOf(minor), rounds)) | ||
.getResult(); | ||
} | ||
|
||
public static String generatePBKDF2Hash(String password, String algorithm, int iterations, int length) { | ||
return Password.hash(CharBuffer.wrap(password.toCharArray())) | ||
.with(CompressedPBKDF2Function.getInstance(algorithm, iterations, length)) | ||
.getResult(); | ||
} | ||
|
||
} |
97 changes: 97 additions & 0 deletions
97
src/integrationTest/java/org/opensearch/security/hash/PBKDF2CustomConfigHashingTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.hash; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.apache.http.HttpStatus; | ||
import org.awaitility.Awaitility; | ||
import org.junit.BeforeClass; | ||
import org.junit.Test; | ||
|
||
import org.opensearch.security.support.ConfigConstants; | ||
import org.opensearch.test.framework.TestSecurityConfig; | ||
import org.opensearch.test.framework.cluster.ClusterManager; | ||
import org.opensearch.test.framework.cluster.LocalCluster; | ||
import org.opensearch.test.framework.cluster.TestRestClient; | ||
|
||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; | ||
import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; | ||
|
||
public class PBKDF2CustomConfigHashingTests extends HashingTests { | ||
|
||
public static LocalCluster cluster; | ||
|
||
private static final String PASSWORD = "top$ecret1234!"; | ||
|
||
private static String function; | ||
private static int iterations, length; | ||
|
||
@BeforeClass | ||
public static void startCluster() { | ||
|
||
function = randomFrom(List.of("SHA224", "SHA256", "SHA384", "SHA512")); | ||
iterations = randomFrom(List.of(32000, 64000, 128000, 256000)); | ||
length = randomFrom(List.of(128, 256, 512)); | ||
|
||
TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS) | ||
.hash(generatePBKDF2Hash("secret", function, iterations, length)); | ||
cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) | ||
.authc(AUTHC_HTTPBASIC_INTERNAL) | ||
.users(ADMIN_USER) | ||
.anonymousAuth(false) | ||
.nodeSettings( | ||
Map.of( | ||
ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, | ||
List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName()), | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, | ||
ConfigConstants.PBKDF2, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION, | ||
function, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS, | ||
iterations, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH, | ||
length | ||
) | ||
) | ||
.build(); | ||
cluster.before(); | ||
|
||
try (TestRestClient client = cluster.getRestClient(ADMIN_USER.getName(), "secret")) { | ||
Awaitility.await() | ||
.alias("Load default configuration") | ||
.until(() -> client.securityHealth().getTextFromJsonBody("/status"), equalTo("UP")); | ||
} | ||
} | ||
|
||
@Test | ||
public void shouldAuthenticateWithCorrectPassword() { | ||
String hash = generatePBKDF2Hash(PASSWORD, function, iterations, length); | ||
createUserWithHashedPassword(cluster, "user_1", hash); | ||
testPasswordAuth(cluster, "user_1", PASSWORD, HttpStatus.SC_OK); | ||
|
||
createUserWithPlainTextPassword(cluster, "user_2", PASSWORD); | ||
testPasswordAuth(cluster, "user_2", PASSWORD, HttpStatus.SC_OK); | ||
} | ||
|
||
@Test | ||
public void shouldNotAuthenticateWithIncorrectPassword() { | ||
String hash = generatePBKDF2Hash(PASSWORD, function, iterations, length); | ||
createUserWithHashedPassword(cluster, "user_3", hash); | ||
testPasswordAuth(cluster, "user_3", "wrong_password", HttpStatus.SC_UNAUTHORIZED); | ||
|
||
createUserWithPlainTextPassword(cluster, "user_4", PASSWORD); | ||
testPasswordAuth(cluster, "user_4", "wrong_password", HttpStatus.SC_UNAUTHORIZED); | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
src/integrationTest/java/org/opensearch/security/hash/PBKDF2DefaultConfigHashingTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.hash; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import org.apache.http.HttpStatus; | ||
import org.junit.ClassRule; | ||
import org.junit.Test; | ||
|
||
import org.opensearch.security.support.ConfigConstants; | ||
import org.opensearch.test.framework.TestSecurityConfig; | ||
import org.opensearch.test.framework.cluster.ClusterManager; | ||
import org.opensearch.test.framework.cluster.LocalCluster; | ||
|
||
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL; | ||
import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; | ||
|
||
public class PBKDF2DefaultConfigHashingTests extends HashingTests { | ||
|
||
private static final TestSecurityConfig.User ADMIN_USER = new TestSecurityConfig.User("admin").roles(ALL_ACCESS) | ||
.hash( | ||
generatePBKDF2Hash( | ||
"secret", | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION_DEFAULT, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS_DEFAULT, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH_DEFAULT | ||
) | ||
); | ||
|
||
@ClassRule | ||
public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) | ||
.authc(AUTHC_HTTPBASIC_INTERNAL) | ||
.users(ADMIN_USER) | ||
.anonymousAuth(false) | ||
.nodeSettings( | ||
Map.of( | ||
ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, | ||
List.of("user_" + ADMIN_USER.getName() + "__" + ALL_ACCESS.getName()), | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, | ||
ConfigConstants.PBKDF2 | ||
) | ||
) | ||
.build(); | ||
|
||
@Test | ||
public void shouldAuthenticateWithCorrectPassword() { | ||
String hash = generatePBKDF2Hash( | ||
PASSWORD, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION_DEFAULT, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS_DEFAULT, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH_DEFAULT | ||
); | ||
createUserWithHashedPassword(cluster, "user_1", hash); | ||
testPasswordAuth(cluster, "user_1", PASSWORD, HttpStatus.SC_OK); | ||
|
||
createUserWithPlainTextPassword(cluster, "user_2", PASSWORD); | ||
testPasswordAuth(cluster, "user_2", PASSWORD, HttpStatus.SC_OK); | ||
} | ||
|
||
@Test | ||
public void shouldNotAuthenticateWithIncorrectPassword() { | ||
String hash = generatePBKDF2Hash( | ||
PASSWORD, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION_DEFAULT, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS_DEFAULT, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH_DEFAULT | ||
); | ||
createUserWithHashedPassword(cluster, "user_3", hash); | ||
testPasswordAuth(cluster, "user_3", "wrong_password", HttpStatus.SC_UNAUTHORIZED); | ||
|
||
createUserWithPlainTextPassword(cluster, "user_4", PASSWORD); | ||
testPasswordAuth(cluster, "user_4", "wrong_password", HttpStatus.SC_UNAUTHORIZED); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
src/main/java/org/opensearch/security/hasher/AbstractPasswordHasher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.hasher; | ||
|
||
import java.nio.CharBuffer; | ||
import java.util.Arrays; | ||
|
||
import org.opensearch.OpenSearchSecurityException; | ||
|
||
import com.password4j.HashingFunction; | ||
|
||
import static org.opensearch.core.common.Strings.isNullOrEmpty; | ||
|
||
/** | ||
* Abstract implementation of PasswordHasher interface | ||
*/ | ||
abstract class AbstractPasswordHasher implements PasswordHasher { | ||
|
||
/** | ||
* The hashing function used by the hasher. | ||
*/ | ||
HashingFunction hashingFunction; | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public abstract String hash(char[] password); | ||
|
||
/** | ||
* {@inheritDoc} | ||
*/ | ||
public abstract boolean check(char[] password, String hash); | ||
|
||
/** | ||
* Clears the given password buffer to prevent sensitive data from being left in memory. | ||
* | ||
* @param password the CharBuffer containing the password to clear | ||
*/ | ||
protected void cleanup(CharBuffer password) { | ||
password.clear(); | ||
char[] passwordOverwrite = new char[password.capacity()]; | ||
Arrays.fill(passwordOverwrite, '\0'); | ||
password.put(passwordOverwrite); | ||
} | ||
|
||
/** | ||
* Checks if the given password is null or empty and throws an exception if it is. | ||
* | ||
* @param password the password to check | ||
* @throws OpenSearchSecurityException if the password is null or empty | ||
*/ | ||
protected void checkPasswordNotNullOrEmpty(char[] password) { | ||
if (password == null || password.length == 0) { | ||
throw new OpenSearchSecurityException("Password cannot be empty or null"); | ||
} | ||
} | ||
|
||
/** | ||
* Checks if the given hash is null or empty and throws an exception if it is. | ||
* | ||
* @param hash the hash to check | ||
* @throws OpenSearchSecurityException if the hash is null or empty | ||
*/ | ||
protected void checkHashNotNullOrEmpty(String hash) { | ||
if (isNullOrEmpty(hash)) { | ||
throw new OpenSearchSecurityException("Hash cannot be empty or null"); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
src/main/java/org/opensearch/security/hasher/PBKDF2PasswordHasher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.hasher; | ||
|
||
import java.nio.CharBuffer; | ||
import java.security.AccessController; | ||
import java.security.PrivilegedAction; | ||
|
||
import org.opensearch.SpecialPermission; | ||
|
||
import com.password4j.CompressedPBKDF2Function; | ||
import com.password4j.HashingFunction; | ||
import com.password4j.Password; | ||
|
||
class PBKDF2PasswordHasher extends AbstractPasswordHasher { | ||
|
||
private static final int DEFAULT_SALT_LENGTH = 128; | ||
|
||
@SuppressWarnings("removal") | ||
PBKDF2PasswordHasher(String function, int iterations, int length) { | ||
SecurityManager securityManager = System.getSecurityManager(); | ||
if (securityManager != null) { | ||
securityManager.checkPermission(new SpecialPermission()); | ||
} | ||
this.hashingFunction = AccessController.doPrivileged( | ||
(PrivilegedAction<HashingFunction>) () -> CompressedPBKDF2Function.getInstance(function, iterations, length) | ||
); | ||
} | ||
|
||
@Override | ||
@SuppressWarnings("removal") | ||
public String hash(char[] password) { | ||
checkPasswordNotNullOrEmpty(password); | ||
CharBuffer passwordBuffer = CharBuffer.wrap(password); | ||
try { | ||
SecurityManager securityManager = System.getSecurityManager(); | ||
if (securityManager != null) { | ||
securityManager.checkPermission(new SpecialPermission()); | ||
} | ||
return AccessController.doPrivileged( | ||
(PrivilegedAction<String>) () -> Password.hash(passwordBuffer) | ||
.addRandomSalt(DEFAULT_SALT_LENGTH) | ||
.with(hashingFunction) | ||
.getResult() | ||
); | ||
} finally { | ||
cleanup(passwordBuffer); | ||
} | ||
} | ||
|
||
@SuppressWarnings("removal") | ||
@Override | ||
public boolean check(char[] password, String hash) { | ||
checkPasswordNotNullOrEmpty(password); | ||
checkHashNotNullOrEmpty(hash); | ||
CharBuffer passwordBuffer = CharBuffer.wrap(password); | ||
try { | ||
SecurityManager securityManager = System.getSecurityManager(); | ||
if (securityManager != null) { | ||
securityManager.checkPermission(new SpecialPermission()); | ||
} | ||
return AccessController.doPrivileged( | ||
(PrivilegedAction<Boolean>) () -> Password.check(passwordBuffer, hash).with(getPBKDF2FunctionFromHash(hash)) | ||
); | ||
} finally { | ||
cleanup(passwordBuffer); | ||
} | ||
} | ||
|
||
private HashingFunction getPBKDF2FunctionFromHash(String hash) { | ||
return CompressedPBKDF2Function.getInstanceFromHash(hash); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
93 changes: 93 additions & 0 deletions
93
src/main/java/org/opensearch/security/hasher/PasswordHasherFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.hasher; | ||
|
||
import java.util.Set; | ||
|
||
import org.opensearch.common.settings.Settings; | ||
import org.opensearch.security.support.ConfigConstants; | ||
|
||
import static org.opensearch.security.support.ConfigConstants.BCRYPT; | ||
import static org.opensearch.security.support.ConfigConstants.PBKDF2; | ||
|
||
public class PasswordHasherFactory { | ||
|
||
private static final Set<String> ALLOWED_BCRYPT_MINORS = Set.of("A", "B", "Y"); | ||
|
||
public static PasswordHasher createPasswordHasher(Settings settings) { | ||
String algorithm = settings.get( | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM_DEFAULT | ||
); | ||
|
||
PasswordHasher passwordHasher; | ||
switch (algorithm.toLowerCase()) { | ||
case BCRYPT: | ||
passwordHasher = getBCryptHasher(settings); | ||
break; | ||
case PBKDF2: | ||
passwordHasher = getPBKDF2Hasher(settings); | ||
break; | ||
default: | ||
throw new IllegalArgumentException(String.format("Password hashing algorithm '%s' not supported.", algorithm)); | ||
} | ||
return passwordHasher; | ||
} | ||
|
||
private static PasswordHasher getBCryptHasher(Settings settings) { | ||
int rounds = settings.getAsInt( | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS_DEFAULT | ||
); | ||
String minor = settings.get( | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_MINOR, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_MINOR_DEFAULT | ||
).toUpperCase(); | ||
|
||
if (rounds < 4 || rounds > 31) { | ||
throw new IllegalArgumentException(String.format("BCrypt rounds must be between 4 and 31. Got: %d", rounds)); | ||
} | ||
if (!ALLOWED_BCRYPT_MINORS.contains(minor)) { | ||
throw new IllegalArgumentException(String.format("BCrypt minor must be 'A', 'B', or 'Y'. Got: %s", minor)); | ||
} | ||
return new BCryptPasswordHasher(minor, rounds); | ||
} | ||
|
||
private static PasswordHasher getPBKDF2Hasher(Settings settings) { | ||
String pbkdf2Function = settings.get( | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION_DEFAULT | ||
).toUpperCase(); | ||
|
||
int iterations = settings.getAsInt( | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS_DEFAULT | ||
); | ||
int length = settings.getAsInt( | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH_DEFAULT | ||
); | ||
|
||
if (!pbkdf2Function.matches("SHA(1|224|256|384|512)")) { | ||
throw new IllegalArgumentException( | ||
String.format("PBKDF2 function must be one of SHA1, SHA224, SHA256, SHA384, or SHA512. Got: %s", pbkdf2Function) | ||
); | ||
} | ||
if (iterations <= 0) { | ||
throw new IllegalArgumentException(String.format("PBKDF2 iterations must be a positive integer. Got: %d", iterations)); | ||
} | ||
if (length <= 0) { | ||
throw new IllegalArgumentException(String.format("PBKDF2 length must be a positive integer. Got: %d", length)); | ||
} | ||
return new PBKDF2PasswordHasher(pbkdf2Function, iterations, length); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
src/test/java/org/opensearch/security/hasher/AbstractPasswordHasherTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.hasher; | ||
|
||
import java.nio.CharBuffer; | ||
|
||
import org.junit.Test; | ||
|
||
import org.opensearch.OpenSearchSecurityException; | ||
|
||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.is; | ||
import static org.hamcrest.Matchers.not; | ||
import static org.junit.Assert.assertThrows; | ||
|
||
public abstract class AbstractPasswordHasherTests { | ||
|
||
PasswordHasher passwordHasher; | ||
|
||
final String password = "testPassword"; | ||
final String wrongPassword = "wrongTestPassword"; | ||
|
||
@Test | ||
public void shouldMatchHashToCorrectPassword() { | ||
String hashedPassword = passwordHasher.hash(password.toCharArray()); | ||
assertThat(passwordHasher.check(password.toCharArray(), hashedPassword), is(true)); | ||
} | ||
|
||
@Test | ||
public void shouldNotMatchHashToWrongPassword() { | ||
String hashedPassword = passwordHasher.hash(password.toCharArray()); | ||
assertThat(passwordHasher.check(wrongPassword.toCharArray(), hashedPassword), is(false)); | ||
|
||
} | ||
|
||
@Test | ||
public void shouldGenerateDifferentHashesForTheSamePassword() { | ||
String hash1 = passwordHasher.hash(password.toCharArray()); | ||
String hash2 = passwordHasher.hash(password.toCharArray()); | ||
assertThat(hash1, is(not(hash2))); | ||
} | ||
|
||
@Test | ||
public void shouldHandleNullPasswordWhenHashing() { | ||
char[] nullPassword = null; | ||
assertThrows(OpenSearchSecurityException.class, () -> { passwordHasher.hash(nullPassword); }); | ||
} | ||
|
||
@Test | ||
public void shouldHandleNullPasswordWhenChecking() { | ||
char[] nullPassword = null; | ||
assertThrows(OpenSearchSecurityException.class, () -> { passwordHasher.check(nullPassword, "some hash"); }); | ||
} | ||
|
||
@Test | ||
public void shouldHandleEmptyHashWhenChecking() { | ||
String emptyHash = ""; | ||
assertThrows(OpenSearchSecurityException.class, () -> { passwordHasher.check(password.toCharArray(), emptyHash); }); | ||
} | ||
|
||
@Test | ||
public void shouldHandleNullHashWhenChecking() { | ||
String nullHash = null; | ||
assertThrows(OpenSearchSecurityException.class, () -> { passwordHasher.check(password.toCharArray(), nullHash); }); | ||
} | ||
|
||
@Test | ||
public void shouldCleanupPasswordCharArray() { | ||
char[] password = new char[] { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' }; | ||
passwordHasher.hash(password); | ||
assertThat("\0\0\0\0\0\0\0\0", is(new String(password))); | ||
} | ||
|
||
@Test | ||
public void shouldCleanupPasswordCharBuffer() { | ||
char[] password = new char[] { 'p', 'a', 's', 's', 'w', 'o', 'r', 'd' }; | ||
CharBuffer passwordBuffer = CharBuffer.wrap(password); | ||
passwordHasher.hash(password); | ||
assertThat("\0\0\0\0\0\0\0\0", is(new String(password))); | ||
assertThat("\0\0\0\0\0\0\0\0", is(passwordBuffer.toString())); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
src/test/java/org/opensearch/security/hasher/PBKDF2PasswordHasherTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.hasher; | ||
|
||
import org.junit.Before; | ||
import org.junit.Test; | ||
|
||
import org.opensearch.security.support.ConfigConstants; | ||
|
||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.is; | ||
|
||
public class PBKDF2PasswordHasherTests extends AbstractPasswordHasherTests { | ||
|
||
@Before | ||
public void setup() { | ||
passwordHasher = new PBKDF2PasswordHasher( | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION_DEFAULT, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS_DEFAULT, | ||
ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH_DEFAULT | ||
); | ||
} | ||
|
||
@Test | ||
public void shouldGenerateValidHashesFromParameters() { | ||
PasswordHasher hasher = new PBKDF2PasswordHasher("SHA1", 150000, 128); | ||
String hash = hasher.hash(password.toCharArray()); | ||
assertThat(hasher.check(password.toCharArray(), hash), is(true)); | ||
assertThat(hasher.check(wrongPassword.toCharArray(), hash), is(false)); | ||
|
||
hasher = new PBKDF2PasswordHasher("SHA224", 100000, 224); | ||
hash = hasher.hash(password.toCharArray()); | ||
assertThat(hasher.check(password.toCharArray(), hash), is(true)); | ||
assertThat(hasher.check(wrongPassword.toCharArray(), hash), is(false)); | ||
|
||
hasher = new PBKDF2PasswordHasher("SHA256", 75000, 256); | ||
hash = hasher.hash(password.toCharArray()); | ||
assertThat(hasher.check(password.toCharArray(), hash), is(true)); | ||
assertThat(hasher.check(wrongPassword.toCharArray(), hash), is(false)); | ||
|
||
hasher = new PBKDF2PasswordHasher("SHA384", 50000, 384); | ||
hash = hasher.hash(password.toCharArray()); | ||
assertThat(hasher.check(password.toCharArray(), hash), is(true)); | ||
assertThat(hasher.check(wrongPassword.toCharArray(), hash), is(false)); | ||
|
||
hasher = new PBKDF2PasswordHasher("SHA512", 10000, 512); | ||
hash = hasher.hash(password.toCharArray()); | ||
assertThat(hasher.check(password.toCharArray(), hash), is(true)); | ||
assertThat(hasher.check(wrongPassword.toCharArray(), hash), is(false)); | ||
} | ||
} |
186 changes: 186 additions & 0 deletions
186
src/test/java/org/opensearch/security/hasher/PasswordHasherFactoryTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.hasher; | ||
|
||
import org.junit.Test; | ||
|
||
import org.opensearch.common.settings.Settings; | ||
import org.opensearch.security.support.ConfigConstants; | ||
|
||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.is; | ||
import static org.junit.Assert.assertThrows; | ||
|
||
public class PasswordHasherFactoryTests { | ||
|
||
@Test | ||
public void shouldReturnBCryptByDefault() { | ||
final Settings settings = Settings.EMPTY; | ||
PasswordHasher passwordHasher = PasswordHasherFactory.createPasswordHasher(settings); | ||
assertThat(passwordHasher instanceof BCryptPasswordHasher, is(true)); | ||
} | ||
|
||
@Test | ||
public void shouldReturnBCryptWhenBCryptSpecified() { | ||
final Settings settings = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.BCRYPT) | ||
.build(); | ||
PasswordHasher passwordHasher = PasswordHasherFactory.createPasswordHasher(settings); | ||
assertThat(passwordHasher instanceof BCryptPasswordHasher, is(true)); | ||
} | ||
|
||
@Test | ||
public void shouldReturnBCryptWhenBCryptWithValidMinorVersionSpecified() { | ||
final Settings settings = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.BCRYPT) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_MINOR, "B") | ||
.build(); | ||
PasswordHasher passwordHasher = PasswordHasherFactory.createPasswordHasher(settings); | ||
assertThat(passwordHasher instanceof BCryptPasswordHasher, is(true)); | ||
} | ||
|
||
@Test | ||
public void shouldReturnBCryptWhenBCryptWithValidLogRoundsSpecified() { | ||
final Settings settings = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.BCRYPT) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_ROUNDS, 8) | ||
.build(); | ||
PasswordHasher passwordHasher = PasswordHasherFactory.createPasswordHasher(settings); | ||
assertThat(passwordHasher instanceof BCryptPasswordHasher, is(true)); | ||
} | ||
|
||
@Test | ||
public void shouldReturnExceptionWhenInvalidBCryptMinorVersionSpecified() { | ||
final Settings settings = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.BCRYPT) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_MINOR, "X") | ||
.build(); | ||
assertThrows(IllegalArgumentException.class, () -> { PasswordHasherFactory.createPasswordHasher(settings); }); | ||
} | ||
|
||
@Test | ||
public void shouldReturnExceptionWhenInvalidBCryptRoundsSpecified() { | ||
final Settings settings = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.BCRYPT) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_MINOR, "3") | ||
.build(); | ||
assertThrows(IllegalArgumentException.class, () -> { PasswordHasherFactory.createPasswordHasher(settings); }); | ||
|
||
final Settings settings2 = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.BCRYPT) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_BCRYPT_MINOR, "32") | ||
.build(); | ||
assertThrows(IllegalArgumentException.class, () -> { PasswordHasherFactory.createPasswordHasher(settings2); }); | ||
|
||
} | ||
|
||
@Test | ||
public void shouldReturnPBKDF2WhenPBKDF2Specified() { | ||
final Settings settings = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.PBKDF2) | ||
.build(); | ||
PasswordHasher passwordHasher = PasswordHasherFactory.createPasswordHasher(settings); | ||
assertThat(passwordHasher instanceof PBKDF2PasswordHasher, is(true)); | ||
} | ||
|
||
@Test | ||
public void shouldReturnPBKDF2WhenPBKDF2WithValidFunctionSpecified() { | ||
final Settings settingsSHA1 = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.PBKDF2) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION, "SHA1") | ||
.build(); | ||
PasswordHasher passwordHasher = PasswordHasherFactory.createPasswordHasher(settingsSHA1); | ||
assertThat(passwordHasher instanceof PBKDF2PasswordHasher, is(true)); | ||
|
||
final Settings settingsSHA224 = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.PBKDF2) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION, "SHA224") | ||
.build(); | ||
passwordHasher = PasswordHasherFactory.createPasswordHasher(settingsSHA224); | ||
assertThat(passwordHasher instanceof PBKDF2PasswordHasher, is(true)); | ||
|
||
final Settings settingsSHA256 = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.PBKDF2) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION, "SHA256") | ||
.build(); | ||
passwordHasher = PasswordHasherFactory.createPasswordHasher(settingsSHA256); | ||
assertThat(passwordHasher instanceof PBKDF2PasswordHasher, is(true)); | ||
|
||
final Settings settingsSHA384 = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.PBKDF2) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION, "SHA384") | ||
.build(); | ||
passwordHasher = PasswordHasherFactory.createPasswordHasher(settingsSHA384); | ||
assertThat(passwordHasher instanceof PBKDF2PasswordHasher, is(true)); | ||
|
||
final Settings settingsSHA512 = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.PBKDF2) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION, "SHA512") | ||
.build(); | ||
passwordHasher = PasswordHasherFactory.createPasswordHasher(settingsSHA512); | ||
assertThat(passwordHasher instanceof PBKDF2PasswordHasher, is(true)); | ||
} | ||
|
||
@Test | ||
public void shouldReturnPBKDF2WhenPBKDF2WithValidIterationsSpecified() { | ||
final Settings settings = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.PBKDF2) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS, 32000) | ||
.build(); | ||
|
||
PasswordHasher passwordHasher = PasswordHasherFactory.createPasswordHasher(settings); | ||
assertThat(passwordHasher instanceof PBKDF2PasswordHasher, is(true)); | ||
} | ||
|
||
@Test | ||
public void shouldReturnPBKDF2WhenPBKDF2WithValidLengthSpecified() { | ||
final Settings settings = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.PBKDF2) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH, 512) | ||
.build(); | ||
PasswordHasher passwordHasher = PasswordHasherFactory.createPasswordHasher(settings); | ||
assertThat(passwordHasher instanceof PBKDF2PasswordHasher, is(true)); | ||
} | ||
|
||
@Test | ||
public void shouldReturnExceptionWhenInvalidPBKDF2FunctionSpecified() { | ||
final Settings settings = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.PBKDF2) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_FUNCTION, "SHA1000") | ||
.build(); | ||
assertThrows(IllegalArgumentException.class, () -> { PasswordHasherFactory.createPasswordHasher(settings); }); | ||
} | ||
|
||
@Test | ||
public void shouldReturnExceptionWhenInvalidPBKDF2IterationsSpecified() { | ||
final Settings settings = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.PBKDF2) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_ITERATIONS, -100000) | ||
.build(); | ||
assertThrows(IllegalArgumentException.class, () -> { PasswordHasherFactory.createPasswordHasher(settings); }); | ||
} | ||
|
||
@Test | ||
public void shouldReturnExceptionWhenInvalidPBKDF2LengthSpecified() { | ||
final Settings settings = Settings.builder() | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, ConfigConstants.PBKDF2) | ||
.put(ConfigConstants.SECURITY_PASSWORD_HASHING_PBKDF2_LENGTH, -100) | ||
.build(); | ||
assertThrows(IllegalArgumentException.class, () -> { PasswordHasherFactory.createPasswordHasher(settings); }); | ||
} | ||
|
||
@Test | ||
public void shouldReturnExceptionWhenInvalidHashingAlgorithmSpecified() { | ||
final Settings settings = Settings.builder().put(ConfigConstants.SECURITY_PASSWORD_HASHING_ALGORITHM, "Invalid").build(); | ||
assertThrows(IllegalArgumentException.class, () -> { PasswordHasherFactory.createPasswordHasher(settings); }); | ||
} | ||
} |
145 changes: 145 additions & 0 deletions
145
src/test/java/org/opensearch/security/tools/HasherTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* The OpenSearch Contributors require contributions made to | ||
* this file be licensed under the Apache-2.0 license or a | ||
* compatible open source license. | ||
* | ||
* Modifications Copyright OpenSearch Contributors. See | ||
* GitHub history for details. | ||
*/ | ||
|
||
package org.opensearch.security.tools; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.InputStream; | ||
import java.io.PrintStream; | ||
|
||
import org.junit.After; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
|
||
import com.password4j.CompressedPBKDF2Function; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
public class HasherTests { | ||
private final ByteArrayOutputStream out = new ByteArrayOutputStream(); | ||
private final PrintStream originalOut = System.out; | ||
private final InputStream originalIn = System.in; | ||
|
||
@Before | ||
public void setOutputStreams() { | ||
System.setOut(new PrintStream(out)); | ||
} | ||
|
||
@After | ||
public void restoreStreams() { | ||
System.setOut(originalOut); | ||
System.setIn(originalIn); | ||
} | ||
|
||
@Test | ||
public void testWithDefaultArguments() { | ||
Hasher.main(new String[] { "-p", "password" }); | ||
assertTrue("should return a valid BCrypt hash with the default BCrypt configuration", out.toString().startsWith("$2y$12")); | ||
} | ||
|
||
@Test | ||
public void testWithBCryptRoundsArgument() { | ||
Hasher.main(new String[] { "-p", "password", "-a", "BCrypt", "-r", "5" }); | ||
assertTrue("should return a valid BCrypt hash with the correct value for \"rounds\"", out.toString().startsWith("$2y$05")); | ||
out.reset(); | ||
|
||
Hasher.main(new String[] { "-p", "password", "-a", "BCrypt", "-r", "5" }); | ||
assertTrue("should return a valid BCrypt hash with the correct value for \"rounds\"", out.toString().startsWith("$2y$05")); | ||
} | ||
|
||
@Test | ||
public void testWithBCryptMinorArgument() { | ||
Hasher.main(new String[] { "-p", "password", "-a", "BCrypt", "-min", "A" }); | ||
assertTrue("should return a valid BCrypt hash with the correct value for \"minor\"", out.toString().startsWith("$2a$12")); | ||
out.reset(); | ||
|
||
Hasher.main(new String[] { "-p", "password", "-a", "BCrypt", "-min", "Y" }); | ||
assertTrue("should return a valid BCrypt hash with the correct value for \"minor\"", out.toString().startsWith("$2y$12")); | ||
out.reset(); | ||
|
||
Hasher.main(new String[] { "-p", "password", "-a", "BCrypt", "-min", "B" }); | ||
assertTrue("should return a valid BCrypt hash with the correct value for \"minor\"", out.toString().startsWith("$2b$12")); | ||
out.reset(); | ||
} | ||
|
||
@Test | ||
public void testWithBCryptAllArguments() { | ||
Hasher.main(new String[] { "-p", "password", "-a", "BCrypt", "-min", "A", "-r", "5" }); | ||
assertTrue("should return a valid BCrypt hash with the correct configuration", out.toString().startsWith("$2a$05")); | ||
} | ||
|
||
@Test | ||
public void testWithPBKDF2DefaultArguments() { | ||
Hasher.main(new String[] { "-p", "password", "-a", "PBKDF2" }); | ||
CompressedPBKDF2Function pbkdf2Function = CompressedPBKDF2Function.getInstanceFromHash(out.toString()); | ||
assertEquals("should return a valid PBKDF2 hash with the correct value for \"function\"", pbkdf2Function.getAlgorithm(), "SHA256"); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"iterations\"", pbkdf2Function.getIterations(), 600000); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"length\"", pbkdf2Function.getLength(), 256); | ||
} | ||
|
||
@Test | ||
public void testWithPBKDF2FunctionArgument() { | ||
Hasher.main(new String[] { "-p", "password", "-a", "PBKDF2", "-f", "SHA512" }); | ||
CompressedPBKDF2Function pbkdf2Function = CompressedPBKDF2Function.getInstanceFromHash(out.toString()); | ||
assertEquals("should return a valid PBKDF2 hash with the correct value for \"function\"", pbkdf2Function.getAlgorithm(), "SHA512"); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"iterations\"", pbkdf2Function.getIterations(), 600000); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"length\"", pbkdf2Function.getLength(), 256); | ||
out.reset(); | ||
|
||
Hasher.main(new String[] { "-p", "password", "-a", "PBKDF2", "-f", "SHA384" }); | ||
pbkdf2Function = CompressedPBKDF2Function.getInstanceFromHash(out.toString()); | ||
assertEquals("should return a valid PBKDF2 hash with the correct value for \"function\"", pbkdf2Function.getAlgorithm(), "SHA384"); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"iterations\"", pbkdf2Function.getIterations(), 600000); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"length\"", pbkdf2Function.getLength(), 256); | ||
} | ||
|
||
@Test | ||
public void testWithPBKDF2IterationsArgument() { | ||
Hasher.main(new String[] { "-p", "password", "-a", "PBKDF2", "-i", "100000" }); | ||
CompressedPBKDF2Function pbkdf2Function = CompressedPBKDF2Function.getInstanceFromHash(out.toString()); | ||
assertEquals("should return a valid PBKDF2 hash with the correct value for \"function\"", pbkdf2Function.getAlgorithm(), "SHA256"); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"iterations\"", pbkdf2Function.getIterations(), 100000); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"length\"", pbkdf2Function.getLength(), 256); | ||
out.reset(); | ||
|
||
Hasher.main(new String[] { "-p", "password", "-a", "PBKDF2", "-i", "200000" }); | ||
pbkdf2Function = CompressedPBKDF2Function.getInstanceFromHash(out.toString()); | ||
assertEquals("should return a valid PBKDF2 hash with the correct value for \"function\"", pbkdf2Function.getAlgorithm(), "SHA256"); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"iterations\"", pbkdf2Function.getIterations(), 200000); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"length\"", pbkdf2Function.getLength(), 256); | ||
} | ||
|
||
@Test | ||
public void testWithPBKDF2LengthArgument() { | ||
Hasher.main(new String[] { "-p", "password", "-a", "PBKDF2", "-l", "400" }); | ||
CompressedPBKDF2Function pbkdf2Function = CompressedPBKDF2Function.getInstanceFromHash(out.toString()); | ||
assertEquals("should return a valid PBKDF2 hash with the correct value for \"function\"", pbkdf2Function.getAlgorithm(), "SHA256"); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"iterations\"", pbkdf2Function.getIterations(), 600000); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"length\"", pbkdf2Function.getLength(), 400); | ||
out.reset(); | ||
|
||
Hasher.main(new String[] { "-p", "password", "-a", "PBKDF2", "-l", "300" }); | ||
pbkdf2Function = CompressedPBKDF2Function.getInstanceFromHash(out.toString()); | ||
assertEquals("should return a valid PBKDF2 hash with the correct value for \"function\"", pbkdf2Function.getAlgorithm(), "SHA256"); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"iterations\"", pbkdf2Function.getIterations(), 600000); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"length\"", pbkdf2Function.getLength(), 300); | ||
} | ||
|
||
@Test | ||
public void testWithPBKDF2AllArguments() { | ||
Hasher.main(new String[] { "-p", "password", "-a", "PBKDF2", "-l", "250", "-i", "150000", "-f", "SHA384" }); | ||
CompressedPBKDF2Function pbkdf2Function = CompressedPBKDF2Function.getInstanceFromHash(out.toString()); | ||
assertEquals("should return a valid PBKDF2 hash with the correct value for \"function\"", pbkdf2Function.getAlgorithm(), "SHA384"); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"iterations\"", pbkdf2Function.getIterations(), 150000); | ||
assertEquals("should return a valid PBKDF2 hash with the default value for \"length\"", pbkdf2Function.getLength(), 250); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
--- | ||
_meta: | ||
type: "internalusers" | ||
config_version: 2 | ||
admin: | ||
hash: "$3$1331439861760512$wBFrJJIAokWuJxlO6BQPLashXgznvR4tRmXk3aEy9SpHWrb9kFjPPLByZZzMLBNQFjhepgbngYh7RfMh8TrPLw==$vqGlzGsxqGf9TgfxhORjdoqRFB3npvBd9B0GAtBMg9mD2zBbSTohRYlOxUL7UQLma66zZdD67c4RNE9BKelabw==" | ||
reserved: false | ||
hidden: false | ||
backend_roles: [] | ||
attributes: {} | ||
description: "Hashed with PBKDF2 with standard configuration" | ||
user1: | ||
hash: "$1$4294967296128$VmnDMbQ4wLiFUq178RKvj+EYfdb3Q26qCiDcJDoCxpYnKpyuG0JhTC2wpUkMUveV5RmBFzldKQkdqZEfE0XAgg==$9u3aMWc6VP2oGkXei7UaXA==" | ||
reserved: false | ||
hidden: false | ||
backend_roles: [] | ||
attributes: {} | ||
description: "Hashed with PBKDF2 with: SHA1 function | 1000 iterations | 128 length" | ||
user2: | ||
hash: "$2$214748364800224$eQgqv2RI6yo95yeVnM5sfwUCwxHo6re0w+wpx6ZqZtHQV+dzlyP6YFitjNG2mlaTkg0pR56xArQaAgapdVcBQQ==$tGHWhoc83cd5nZ7QIZKPORjW/N5jklhYhRgXpw==" | ||
reserved: false | ||
hidden: false | ||
backend_roles: [] | ||
attributes: {} | ||
description: "Hashed with PBKDF2 with: SHA224 function | 50000 iterations | 224 length" | ||
user3: | ||
hash: "$3$322122547200256$5b3wEAsMc05EZFxfncCUfZRERgvbwlBhYXd5vVR14kNJtmhXSpYMzydRZxO9096IPTkc47doH4hIdKX6LTguLg==$oQQvAtUyOC6cwdAYi5WeIM7rGUN9l3IdJ9y2RNxZCWo=" | ||
reserved: false | ||
hidden: false | ||
backend_roles: [] | ||
attributes: {} | ||
description: "Hashed with PBKDF2 with: SHA256 function | 75000 iterations | 256 length" | ||
user4: | ||
hash: "$4$429496729600384$+SNSgbZD67a1bd92iuEiHCq5pvvrCx3HrNIf5hbGIJdxgXegpWilpB6vUGvYigegAUzZqE9iIsL4pSJztUNJYw==$lTxZ7tax6dBQ0r4qPJpc8d7YuoTBiUujY9HJeAZvARXMjIgvnJwa6FeYugttOKc0" | ||
reserved: false | ||
hidden: false | ||
backend_roles: [] | ||
attributes: {} | ||
description: "Hashed with PBKDF2 with: SHA384 function | 100000 iterations | 384 length" | ||
user5: | ||
hash: "$5$644245094400512$HQe/MOv/NAlgodNhqTmjqj5jGxBwG5xuRaxKwn7r4nlUba1kj/CYnpdFaXGvVeRxt2NLm8fbekS6NYonv358Ew==$1sDx+0tMbtGzU6jlQg/Dyt30Yxuy5RdNmP9B1EzMTxYWi8k1xg2gXLy7w1XbetEC8UD/lpyXJPlaoxXpsaADyA==" | ||
reserved: false | ||
hidden: false | ||
backend_roles: [] | ||
attributes: {} | ||
description: "Hashed with PBKDF2 with: SHA512 function | 150000 iterations | 512 length" |