password hash
This commit is contained in:
parent
95b830322a
commit
045361ade5
@ -0,0 +1,89 @@
|
|||||||
|
package net.hostsharing.hsadminng.hash;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Usage-example to generate hash:
|
||||||
|
* HashGenerator.using(LINUX_SHA512).withRandomSalt().hash("plaintext password");
|
||||||
|
*
|
||||||
|
* Usage-example to verify hash:
|
||||||
|
* HashGenerator.fromHash("hashed password).verify("plaintext password");
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public final class HashGenerator {
|
||||||
|
|
||||||
|
private static final RandomGenerator random = new SecureRandom();
|
||||||
|
private static final Queue<String> predefinedSalts = new PriorityQueue<>();
|
||||||
|
|
||||||
|
public static final int RANDOM_SALT_LENGTH = 16;
|
||||||
|
private static final String RANDOM_SALT_CHARACTERS =
|
||||||
|
"abcdefghijklmnopqrstuvwxyz" +
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||||
|
"0123456789/.";
|
||||||
|
|
||||||
|
public enum Algorithm {
|
||||||
|
LINUX_SHA512(LinuxEtcShadowHashGenerator::hash, "6"),
|
||||||
|
LINUX_YESCRYPT(LinuxEtcShadowHashGenerator::hash, "y"),
|
||||||
|
MYSQL_NATIVE(MySQLNativePasswordHashGenerator::hash, "*");
|
||||||
|
|
||||||
|
final BiFunction<HashGenerator, String, String> implementation;
|
||||||
|
final String prefix;
|
||||||
|
|
||||||
|
Algorithm(BiFunction<HashGenerator, String, String> implementation, final String prefix) {
|
||||||
|
this.implementation = implementation;
|
||||||
|
this.prefix = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Algorithm byPrefix(final String prefix) {
|
||||||
|
return Arrays.stream(Algorithm.values()).filter(a -> a.prefix.equals(prefix)).findAny()
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("unknown hash algorithm: '" + prefix + "'"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Algorithm algorithm;
|
||||||
|
private String salt;
|
||||||
|
|
||||||
|
public static HashGenerator using(final Algorithm algorithm) {
|
||||||
|
return new HashGenerator(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HashGenerator(final Algorithm algorithm) {
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String hash(final String plaintextPassword) {
|
||||||
|
if (plaintextPassword == null) {
|
||||||
|
throw new IllegalStateException("no password given");
|
||||||
|
}
|
||||||
|
|
||||||
|
return algorithm.implementation.apply(this, plaintextPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void nextSalt(final String salt) {
|
||||||
|
predefinedSalts.add(salt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashGenerator withSalt(final String salt) {
|
||||||
|
this.salt = salt;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashGenerator withRandomSalt() {
|
||||||
|
if (!predefinedSalts.isEmpty()) {
|
||||||
|
return withSalt(predefinedSalts.poll());
|
||||||
|
}
|
||||||
|
final var stringBuilder = new StringBuilder(RANDOM_SALT_LENGTH);
|
||||||
|
for (int i = 0; i < RANDOM_SALT_LENGTH; ++i) {
|
||||||
|
int randomIndex = random.nextInt(RANDOM_SALT_CHARACTERS.length());
|
||||||
|
stringBuilder.append(RANDOM_SALT_CHARACTERS.charAt(randomIndex));
|
||||||
|
}
|
||||||
|
return withSalt(stringBuilder.toString());
|
||||||
|
}
|
||||||
|
}
|
@ -1,107 +1,31 @@
|
|||||||
package net.hostsharing.hsadminng.hash;
|
package net.hostsharing.hsadminng.hash;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.PriorityQueue;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.random.RandomGenerator;
|
|
||||||
|
|
||||||
import com.sun.jna.Library;
|
import com.sun.jna.Library;
|
||||||
import com.sun.jna.Native;
|
import com.sun.jna.Native;
|
||||||
|
|
||||||
public class LinuxEtcShadowHashGenerator {
|
public class LinuxEtcShadowHashGenerator {
|
||||||
|
|
||||||
private static final RandomGenerator random = new SecureRandom();
|
public static String hash(final HashGenerator generator, final String payload) {
|
||||||
private static final Queue<String> predefinedSalts = new PriorityQueue<>();
|
if (generator.getSalt() == null) {
|
||||||
|
|
||||||
public static final int SALT_LENGTH = 16;
|
|
||||||
|
|
||||||
private final String plaintextPassword;
|
|
||||||
private Algorithm algorithm;
|
|
||||||
|
|
||||||
public enum Algorithm {
|
|
||||||
SHA512("6"),
|
|
||||||
YESCRYPT("y");
|
|
||||||
|
|
||||||
final String prefix;
|
|
||||||
|
|
||||||
Algorithm(final String prefix) {
|
|
||||||
this.prefix = prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Algorithm byPrefix(final String prefix) {
|
|
||||||
return Arrays.stream(Algorithm.values()).filter(a -> a.prefix.equals(prefix)).findAny()
|
|
||||||
.orElseThrow(() -> new IllegalArgumentException("unknown hash algorithm: '" + prefix + "'"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String SALT_CHARACTERS =
|
|
||||||
"abcdefghijklmnopqrstuvwxyz" +
|
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
|
||||||
"0123456789/.";
|
|
||||||
|
|
||||||
private String salt;
|
|
||||||
|
|
||||||
public static LinuxEtcShadowHashGenerator hash(final String plaintextPassword) {
|
|
||||||
return new LinuxEtcShadowHashGenerator(plaintextPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LinuxEtcShadowHashGenerator(final String plaintextPassword) {
|
|
||||||
this.plaintextPassword = plaintextPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LinuxEtcShadowHashGenerator using(final Algorithm algorithm) {
|
|
||||||
this.algorithm = algorithm;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void verify(final String givenHash) {
|
|
||||||
final var parts = givenHash.split("\\$");
|
|
||||||
if (parts.length < 3 || parts.length > 5) {
|
|
||||||
throw new IllegalArgumentException("not a " + algorithm.name() + " Linux hash: " + givenHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
algorithm = Algorithm.byPrefix(parts[1]);
|
|
||||||
salt = parts.length == 4 ? parts[2] : parts[2] + "$" + parts[3];
|
|
||||||
|
|
||||||
if (!generate().equals(givenHash)) {
|
|
||||||
throw new IllegalArgumentException("invalid password");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String generate() {
|
|
||||||
if (salt == null) {
|
|
||||||
throw new IllegalStateException("no salt given");
|
throw new IllegalStateException("no salt given");
|
||||||
}
|
}
|
||||||
if (plaintextPassword == null) {
|
|
||||||
throw new IllegalStateException("no password given");
|
return NativeCryptLibrary.INSTANCE.crypt(payload, "$" + generator.getAlgorithm().prefix + "$" + generator.getSalt());
|
||||||
}
|
}
|
||||||
|
|
||||||
return NativeCryptLibrary.INSTANCE.crypt(plaintextPassword, "$" + algorithm.prefix + "$" + salt);
|
public static void verify(final String givenHash, final String payload) {
|
||||||
|
|
||||||
|
final var parts = givenHash.split("\\$");
|
||||||
|
if (parts.length < 3 || parts.length > 5) {
|
||||||
|
throw new IllegalArgumentException("hash with unknown hash method: " + givenHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void nextSalt(final String salt) {
|
final var algorithm = HashGenerator.Algorithm.byPrefix(parts[1]);
|
||||||
predefinedSalts.add(salt);
|
final var salt = parts.length == 4 ? parts[2] : parts[2] + "$" + parts[3];
|
||||||
|
final var calcualatedHash = HashGenerator.using(algorithm).withSalt(salt).hash(payload);
|
||||||
|
if (!calcualatedHash.equals(givenHash)) {
|
||||||
|
throw new IllegalArgumentException("invalid password");
|
||||||
}
|
}
|
||||||
|
|
||||||
public LinuxEtcShadowHashGenerator withSalt(final String salt) {
|
|
||||||
this.salt = salt;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LinuxEtcShadowHashGenerator withRandomSalt() {
|
|
||||||
if (!predefinedSalts.isEmpty()) {
|
|
||||||
return withSalt(predefinedSalts.poll());
|
|
||||||
}
|
|
||||||
final var stringBuilder = new StringBuilder(SALT_LENGTH);
|
|
||||||
for (int i = 0; i < SALT_LENGTH; ++i) {
|
|
||||||
int randomIndex = random.nextInt(SALT_CHARACTERS.length());
|
|
||||||
stringBuilder.append(SALT_CHARACTERS.charAt(randomIndex));
|
|
||||||
}
|
|
||||||
return withSalt(stringBuilder.toString());
|
|
||||||
}
|
|
||||||
public static void main(String[] args) {
|
|
||||||
System.out.println(NativeCryptLibrary.INSTANCE.crypt("given password", "$6$abcdefghijklmno"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface NativeCryptLibrary extends Library {
|
public interface NativeCryptLibrary extends Library {
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
package net.hostsharing.hsadminng.hash;
|
||||||
|
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
public class MySQLNativePasswordHashGenerator {
|
||||||
|
|
||||||
|
public static String hash(final HashGenerator generator, final String password) {
|
||||||
|
// TODO.impl: if a random salt is generated or not should be part of the algorithm definition
|
||||||
|
// if (generator.getSalt() != null) {
|
||||||
|
// throw new IllegalStateException("salt not supported");
|
||||||
|
// }
|
||||||
|
|
||||||
|
try {
|
||||||
|
final var sha1 = MessageDigest.getInstance("SHA-1");
|
||||||
|
final var firstHash = sha1.digest(password.getBytes());
|
||||||
|
final var secondHash = sha1.digest(firstHash);
|
||||||
|
return "*" + bytesToHex(secondHash).toUpperCase();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException("SHA-1 algorithm not found", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String bytesToHex(byte[] bytes) {
|
||||||
|
final var hexString = new StringBuilder();
|
||||||
|
for (byte b : bytes) {
|
||||||
|
final var hex = Integer.toHexString(0xff & b);
|
||||||
|
if (hex.length() == 1) {
|
||||||
|
hexString.append('0');
|
||||||
|
}
|
||||||
|
hexString.append(hex);
|
||||||
|
}
|
||||||
|
return hexString.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator;
|
import net.hostsharing.hsadminng.hash.HashGenerator;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -22,7 +22,7 @@ class HsMariaDbUserHostingAssetValidator extends HostingAssetEntityValidator {
|
|||||||
// referenceProperty(assignedToAsset).isWriteOnce(),
|
// referenceProperty(assignedToAsset).isWriteOnce(),
|
||||||
// );
|
// );
|
||||||
|
|
||||||
passwordProperty("password").minLength(8).maxLength(40).hashedUsing(LinuxEtcShadowHashGenerator.Algorithm.SHA512).writeOnly());
|
passwordProperty("password").minLength(8).maxLength(40).hashedUsing(HashGenerator.Algorithm.MYSQL_NATIVE).writeOnly());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator;
|
import net.hostsharing.hsadminng.hash.HashGenerator;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||||
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
||||||
@ -30,7 +30,8 @@ class HsUnixUserHostingAssetValidator extends HostingAssetEntityValidator {
|
|||||||
.withDefault("/bin/false"),
|
.withDefault("/bin/false"),
|
||||||
stringProperty("homedir").readOnly().computedBy(HsUnixUserHostingAssetValidator::computeHomedir),
|
stringProperty("homedir").readOnly().computedBy(HsUnixUserHostingAssetValidator::computeHomedir),
|
||||||
stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).undisclosed().writeOnly().optional(),
|
stringProperty("totpKey").matchesRegEx("^0x([0-9A-Fa-f]{2})+$").minLength(20).maxLength(256).undisclosed().writeOnly().optional(),
|
||||||
passwordProperty("password").minLength(8).maxLength(40).hashedUsing(LinuxEtcShadowHashGenerator.Algorithm.SHA512).writeOnly());
|
passwordProperty("password").minLength(8).maxLength(40).hashedUsing(HashGenerator.Algorithm.LINUX_SHA512).writeOnly());
|
||||||
|
// TODO.spec: private SSH keys?
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package net.hostsharing.hsadminng.hs.validation;
|
package net.hostsharing.hsadminng.hs.validation;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.Algorithm;
|
import net.hostsharing.hsadminng.hash.HashGenerator;
|
||||||
|
import net.hostsharing.hsadminng.hash.HashGenerator.Algorithm;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.util.Optional.ofNullable;
|
import static java.util.Optional.ofNullable;
|
||||||
import static net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.hash;
|
|
||||||
import static net.hostsharing.hsadminng.mapper.Array.insertNewEntriesAfterExistingEntry;
|
import static net.hostsharing.hsadminng.mapper.Array.insertNewEntriesAfterExistingEntry;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
@ -36,7 +36,7 @@ public class PasswordProperty extends StringProperty<PasswordProperty> {
|
|||||||
this.hashedUsing = algorithm;
|
this.hashedUsing = algorithm;
|
||||||
computedBy((entity)
|
computedBy((entity)
|
||||||
-> ofNullable(entity.getDirectValue(propertyName, String.class))
|
-> ofNullable(entity.getDirectValue(propertyName, String.class))
|
||||||
.map(password -> hash(password).using(algorithm).withRandomSalt().generate())
|
.map(password -> HashGenerator.using(algorithm).withRandomSalt().hash(password))
|
||||||
.orElse(null));
|
.orElse(null));
|
||||||
return self();
|
return self();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
package net.hostsharing.hsadminng.hash;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.LINUX_SHA512;
|
||||||
|
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.MYSQL_NATIVE;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
|
|
||||||
|
class HashGeneratorUnitTest {
|
||||||
|
|
||||||
|
final String GIVEN_PASSWORD = "given password";
|
||||||
|
final String WRONG_PASSWORD = "wrong password";
|
||||||
|
final String GIVEN_SALT = "0123456789abcdef";
|
||||||
|
|
||||||
|
// generated via mkpasswd for plaintext password GIVEN_PASSWORD (see above)
|
||||||
|
final String GIVEN_LINUX_SHA512_HASH = "$6$ooei1HK6JXVaI7KC$sY5d9fEOr36hjh4CYwIKLMfRKL1539bEmbVCZ.zPiH0sv7jJVnoIXb5YEefEtoSM2WWgDi9hr7vXRe3Nw8zJP/";
|
||||||
|
final String GIVEN_LINUX_YESCRYPT_HASH = "$y$j9T$wgYACPmBXvlMg2MzeZA0p1$KXUzd28nG.67GhPnBZ3aZsNNA5bWFdL/dyG4wS0iRw7";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifiesLinuxPasswordAgainstSha512HashFromMkpasswd() {
|
||||||
|
LinuxEtcShadowHashGenerator.verify(GIVEN_LINUX_SHA512_HASH, GIVEN_PASSWORD); // throws exception if wrong
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifiesLinuxPasswordAgainstYescryptHashFromMkpasswd() {
|
||||||
|
LinuxEtcShadowHashGenerator.verify(GIVEN_LINUX_YESCRYPT_HASH, GIVEN_PASSWORD); // throws exception if wrong
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifiesHashedLinuxPasswordWithRandomSalt() {
|
||||||
|
final var hash = HashGenerator.using(LINUX_SHA512).withRandomSalt().hash(GIVEN_PASSWORD);
|
||||||
|
LinuxEtcShadowHashGenerator.verify(hash, GIVEN_PASSWORD); // throws exception if wrong
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifiesLinuxHashedPasswordWithGivenSalt() {
|
||||||
|
final var hash = HashGenerator.using(LINUX_SHA512).withSalt(GIVEN_SALT).hash(GIVEN_PASSWORD);
|
||||||
|
LinuxEtcShadowHashGenerator.verify(hash, GIVEN_PASSWORD); // throws exception if wrong
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void throwsExceptionForInvalidLinuxPassword() {
|
||||||
|
final var hash = HashGenerator.using(LINUX_SHA512).withRandomSalt().hash(GIVEN_PASSWORD);
|
||||||
|
final var throwable = catchThrowable(() ->
|
||||||
|
LinuxEtcShadowHashGenerator.verify(hash, WRONG_PASSWORD)
|
||||||
|
);
|
||||||
|
assertThat(throwable).hasMessage("invalid password");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifiesMySqlNativePassword() {
|
||||||
|
final var hash = HashGenerator.using(MYSQL_NATIVE).hash("Test1234");
|
||||||
|
assertThat(hash).isEqualTo("*14F1A8C42F8B6D4662BB3ED290FD37BF135FE45C");
|
||||||
|
}
|
||||||
|
}
|
@ -1,51 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hash;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.Algorithm.SHA512;
|
|
||||||
import static net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.hash;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
|
||||||
|
|
||||||
class LinuxEtcShadowHashGeneratorUnitTest {
|
|
||||||
|
|
||||||
final String GIVEN_PASSWORD = "given password";
|
|
||||||
final String WRONG_PASSWORD = "wrong password";
|
|
||||||
final String GIVEN_SALT = "0123456789abcdef";
|
|
||||||
|
|
||||||
// generated via mkpasswd for plaintext password GIVEN_PASSWORD (see above)
|
|
||||||
final String GIVEN_SHA512_HASH = "$6$ooei1HK6JXVaI7KC$sY5d9fEOr36hjh4CYwIKLMfRKL1539bEmbVCZ.zPiH0sv7jJVnoIXb5YEefEtoSM2WWgDi9hr7vXRe3Nw8zJP/";
|
|
||||||
final String GIVEN_YESCRYPT_HASH = "$y$j9T$wgYACPmBXvlMg2MzeZA0p1$KXUzd28nG.67GhPnBZ3aZsNNA5bWFdL/dyG4wS0iRw7";
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void verifiesPasswordAgainstSha512HashFromMkpasswd() {
|
|
||||||
hash(GIVEN_PASSWORD).verify(GIVEN_SHA512_HASH); // throws exception if wrong
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void verifiesPasswordAgainstYescryptHashFromMkpasswd() {
|
|
||||||
hash(GIVEN_PASSWORD).verify(GIVEN_YESCRYPT_HASH); // throws exception if wrong
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void verifiesHashedPasswordWithRandomSalt() {
|
|
||||||
final var hash = hash(GIVEN_PASSWORD).using(SHA512).withRandomSalt().generate();
|
|
||||||
hash(GIVEN_PASSWORD).verify(hash); // throws exception if wrong
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void verifiesHashedPasswordWithGivenSalt() {
|
|
||||||
final var givenPasswordHash =hash(GIVEN_PASSWORD).using(SHA512).withSalt(GIVEN_SALT).generate();
|
|
||||||
hash(GIVEN_PASSWORD).verify(givenPasswordHash); // throws exception if wrong
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void throwsExceptionForInvalidPassword() {
|
|
||||||
final var givenPasswordHash = hash(GIVEN_PASSWORD).using(SHA512).withRandomSalt().generate();
|
|
||||||
|
|
||||||
final var throwable = catchThrowable(() ->
|
|
||||||
hash(WRONG_PASSWORD).verify(givenPasswordHash) // throws exception if wrong);
|
|
||||||
);
|
|
||||||
assertThat(throwable).hasMessage("invalid password");
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset;
|
|||||||
import io.restassured.RestAssured;
|
import io.restassured.RestAssured;
|
||||||
import io.restassured.http.ContentType;
|
import io.restassured.http.ContentType;
|
||||||
import net.hostsharing.hsadminng.HsadminNgApplication;
|
import net.hostsharing.hsadminng.HsadminNgApplication;
|
||||||
import net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator;
|
import net.hostsharing.hsadminng.hash.HashGenerator;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemRepository;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||||
@ -537,7 +537,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
|
|||||||
.identifier("fir01-temp")
|
.identifier("fir01-temp")
|
||||||
.caption("some test-unix-user")
|
.caption("some test-unix-user")
|
||||||
.build());
|
.build());
|
||||||
LinuxEtcShadowHashGenerator.nextSalt("Jr5w/Y8zo8pCkqg7");
|
HashGenerator.nextSalt("Jr5w/Y8zo8pCkqg7");
|
||||||
|
|
||||||
RestAssured // @formatter:off
|
RestAssured // @formatter:off
|
||||||
.given()
|
.given()
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator;
|
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity.HsHostingAssetEntityBuilder;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity.HsHostingAssetEntityBuilder;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -33,7 +32,7 @@ class HsMariaDbUserHostingAssetValidatorUnitTest {
|
|||||||
.identifier("xyz00_temp")
|
.identifier("xyz00_temp")
|
||||||
.caption("some valid test MariaDB-User")
|
.caption("some valid test MariaDB-User")
|
||||||
.config(new HashMap<>(ofEntries(
|
.config(new HashMap<>(ofEntries(
|
||||||
entry("password", "Hallo Datenbank, lass mich rein!")
|
entry("password", "Test1234")
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +46,7 @@ class HsMariaDbUserHostingAssetValidatorUnitTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(props).extracting(Object::toString).containsExactlyInAnyOrder(
|
assertThat(props).extracting(Object::toString).containsExactlyInAnyOrder(
|
||||||
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=true, hashedUsing=SHA512, undisclosed=true}"
|
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, computed=true, hashedUsing=MYSQL_NATIVE, undisclosed=true}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,12 +57,12 @@ class HsMariaDbUserHostingAssetValidatorUnitTest {
|
|||||||
final var validator = HostingAssetEntityValidatorRegistry.forType(givenMariaDbUserHostingAsset.getType());
|
final var validator = HostingAssetEntityValidatorRegistry.forType(givenMariaDbUserHostingAsset.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
LinuxEtcShadowHashGenerator.nextSalt("Ly3LbsArtL5u4EVt"); // FIXME
|
// HashGenerator.nextSalt("Ly3LbsArtL5u4EVt"); // not needed for mysql_native_password
|
||||||
validator.prepareProperties(givenMariaDbUserHostingAsset);
|
validator.prepareProperties(givenMariaDbUserHostingAsset);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(givenMariaDbUserHostingAsset.getConfig()).containsExactlyInAnyOrderEntriesOf(ofEntries(
|
assertThat(givenMariaDbUserHostingAsset.getConfig()).containsExactlyInAnyOrderEntriesOf(ofEntries(
|
||||||
entry("password", "$6$Ly3LbsArtL5u4EVt$T7VM5uCq7I1cKttipCX4TQdyawdpLcSmjApreI4fZcORPOkEkxy9iz.9Dri6IVbO08OKTR8OE8hvnblU5Ax6o.")
|
entry("password", "*14F1A8C42F8B6D4662BB3ED290FD37BF135FE45C")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator;
|
import net.hostsharing.hsadminng.hash.HashGenerator;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -50,7 +50,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
|||||||
final var validator = HostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
final var validator = HostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
LinuxEtcShadowHashGenerator.nextSalt("Ly3LbsArtL5u4EVt");
|
HashGenerator.nextSalt("Ly3LbsArtL5u4EVt");
|
||||||
validator.prepareProperties(unixUserHostingAsset);
|
validator.prepareProperties(unixUserHostingAsset);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
@ -141,7 +141,7 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
|||||||
final var validator = HostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
final var validator = HostingAssetEntityValidatorRegistry.forType(unixUserHostingAsset.getType());
|
||||||
|
|
||||||
// when
|
// when
|
||||||
LinuxEtcShadowHashGenerator.nextSalt("Ly3LbsArtL5u4EVt");
|
HashGenerator.nextSalt("Ly3LbsArtL5u4EVt");
|
||||||
final var result = validator.revampProperties(unixUserHostingAsset, unixUserHostingAsset.getConfig());
|
final var result = validator.revampProperties(unixUserHostingAsset, unixUserHostingAsset.getConfig());
|
||||||
|
|
||||||
// then
|
// then
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.validation;
|
package net.hostsharing.hsadminng.hs.validation;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
@ -8,8 +9,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.Algorithm.SHA512;
|
import static net.hostsharing.hsadminng.hash.HashGenerator.Algorithm.LINUX_SHA512;
|
||||||
import static net.hostsharing.hsadminng.hash.LinuxEtcShadowHashGenerator.hash;
|
|
||||||
import static net.hostsharing.hsadminng.hs.validation.PasswordProperty.passwordProperty;
|
import static net.hostsharing.hsadminng.hs.validation.PasswordProperty.passwordProperty;
|
||||||
import static net.hostsharing.hsadminng.mapper.PatchableMapWrapper.entry;
|
import static net.hostsharing.hsadminng.mapper.PatchableMapWrapper.entry;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@ -17,7 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
class PasswordPropertyUnitTest {
|
class PasswordPropertyUnitTest {
|
||||||
|
|
||||||
private final ValidatableProperty<PasswordProperty, String> passwordProp =
|
private final ValidatableProperty<PasswordProperty, String> passwordProp =
|
||||||
passwordProperty("password").minLength(8).maxLength(40).hashedUsing(SHA512).writeOnly();
|
passwordProperty("password").minLength(8).maxLength(40).hashedUsing(LINUX_SHA512).writeOnly();
|
||||||
private final List<String> violations = new ArrayList<>();
|
private final List<String> violations = new ArrayList<>();
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ -115,6 +115,6 @@ class PasswordPropertyUnitTest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// then
|
// then
|
||||||
hash("some password").using(SHA512).withRandomSalt().generate(); // throws exception if wrong
|
LinuxEtcShadowHashGenerator.verify(result, "some password"); // throws exception if wrong
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user