[Major] Refactoring password parsing

This commit is contained in:
Robert von Burg 2023-09-05 14:36:14 +02:00
parent 9f86f84f35
commit ff773e76fd
Signed by: eitch
GPG Key ID: 75DB9C85C74331F7
12 changed files with 416 additions and 713 deletions

View File

@ -73,8 +73,8 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler {
//Specify the search scope
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String searchFilter =
"(&(objectCategory=person)(objectClass=user)(userPrincipalName=" + username + this.domain + "))";
String searchFilter = "(&(objectCategory=person)(objectClass=user)(userPrincipalName=" + username +
this.domain + "))";
// Search for objects using the filter
NamingEnumeration<SearchResult> answer = ctx.search(this.searchBase, searchFilter, searchCtls);
@ -86,8 +86,8 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler {
answer = ctx.search(this.searchBase, searchFilter, searchCtls);
if (!answer.hasMore())
throw new AccessDeniedException("Could not login with user: " + username + this.domain
+ " on Ldap: no LDAP Data, for either userPrincipalName or sAMAccountName");
throw new AccessDeniedException("Could not login with user: " + username + this.domain +
" on Ldap: no LDAP Data, for either userPrincipalName or sAMAccountName");
}
SearchResult searchResult = answer.next();
@ -139,8 +139,8 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler {
Map<String, String> properties = buildProperties(username, attrs, ldapGroups, strolchRoles);
return new User(username, username, null, null, null, -1, -1, firstName, lastName, UserState.REMOTE,
strolchRoles, locale, properties, false, new UserHistory());
return new User(username, username, null, firstName, lastName, UserState.REMOTE, strolchRoles, locale,
properties, false, new UserHistory());
}
protected abstract Map<String, String> buildProperties(String username, Attributes attrs, Set<String> ldapGroups,

View File

@ -15,9 +15,12 @@
*/
package li.strolch.privilege.handler;
import static java.lang.String.valueOf;
import static li.strolch.privilege.base.PrivilegeConstants.*;
import static li.strolch.privilege.helper.XmlConstants.*;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.helper.XmlConstants;
import li.strolch.privilege.model.internal.PasswordCrypt;
import li.strolch.utils.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
@ -29,19 +32,16 @@ import java.security.spec.InvalidKeySpecException;
import java.text.MessageFormat;
import java.util.Map;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.helper.Crypt;
import li.strolch.privilege.helper.XmlConstants;
import li.strolch.utils.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.lang.String.valueOf;
import static li.strolch.privilege.base.PrivilegeConstants.*;
import static li.strolch.privilege.helper.XmlConstants.*;
/**
* <p>
* This default {@link EncryptionHandler} creates tokens using a {@link SecureRandom} object. Hashing is done by using
* {@link MessageDigest} and the configured algorithm which is passed in the parameters
* </p>
*
* <p>
* Required parameters:
* <ul>
* <li>{@link XmlConstants#XML_PARAM_HASH_ALGORITHM}</li>
@ -87,11 +87,6 @@ public class DefaultEncryptionHandler implements EncryptionHandler {
return this.parameterMap;
}
@Override
public Crypt newCryptInstance() {
return new Crypt().setAlgorithm(this.algorithm).setIterations(this.iterations).setKeyLength(this.keyLength);
}
@Override
public String getAlgorithm() {
return this.algorithm;
@ -122,11 +117,11 @@ public class DefaultEncryptionHandler implements EncryptionHandler {
}
@Override
public byte[] hashPasswordWithoutSalt(char[] password) {
public PasswordCrypt hashPasswordWithoutSalt(char[] password) {
try {
MessageDigest digest = MessageDigest.getInstance(this.nonSaltAlgorithm);
return digest.digest(new String(password).getBytes());
return new PasswordCrypt(digest.digest(new String(password).getBytes()), null);
} catch (NoSuchAlgorithmException e) {
throw new PrivilegeException(MessageFormat.format("Algorithm {0} was not found!", nonSaltAlgorithm),
@ -135,24 +130,32 @@ public class DefaultEncryptionHandler implements EncryptionHandler {
}
@Override
public byte[] hashPassword(char[] password, byte[] salt) {
public PasswordCrypt hashPassword(char[] password, byte[] salt) {
return hashPassword(password, salt, this.algorithm, this.iterations, this.keyLength);
}
@Override
public byte[] hashPassword(char[] password, byte[] salt, String algorithm, int iterations, int keyLength) {
public PasswordCrypt hashPassword(char[] password, byte[] salt, String algorithm, int iterations, int keyLength) {
try {
SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLength);
SecretKey key = skf.generateSecret(spec);
return key.getEncoded();
return new PasswordCrypt(key.getEncoded(), salt, algorithm, iterations, keyLength);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IllegalStateException(e);
}
}
@Override
public boolean isPasswordCryptOutdated(PasswordCrypt passwordCrypt) {
return passwordCrypt.getSalt() == null || passwordCrypt.getHashAlgorithm() == null ||
passwordCrypt.getHashIterations() != this.iterations ||
passwordCrypt.getHashKeyLength() != this.keyLength;
}
@Override
public void initialize(Map<String, String> parameterMap) {
this.parameterMap = parameterMap;
@ -161,18 +164,18 @@ public class DefaultEncryptionHandler implements EncryptionHandler {
// get hash algorithm parameters
this.algorithm = parameterMap.getOrDefault(XML_PARAM_HASH_ALGORITHM, DEFAULT_ALGORITHM);
this.nonSaltAlgorithm = parameterMap
.getOrDefault(XML_PARAM_HASH_ALGORITHM_NON_SALT, DEFAULT_ALGORITHM_NON_SALT);
this.iterations = Integer
.parseInt(parameterMap.getOrDefault(XML_PARAM_HASH_ITERATIONS, valueOf(DEFAULT_ITERATIONS)));
this.keyLength = Integer
.parseInt(parameterMap.getOrDefault(XML_PARAM_HASH_KEY_LENGTH, valueOf(DEFAULT_KEY_LENGTH)));
this.nonSaltAlgorithm = parameterMap.getOrDefault(XML_PARAM_HASH_ALGORITHM_NON_SALT,
DEFAULT_ALGORITHM_NON_SALT);
this.iterations = Integer.parseInt(
parameterMap.getOrDefault(XML_PARAM_HASH_ITERATIONS, valueOf(DEFAULT_ITERATIONS)));
this.keyLength = Integer.parseInt(
parameterMap.getOrDefault(XML_PARAM_HASH_KEY_LENGTH, valueOf(DEFAULT_KEY_LENGTH)));
// test non-salt hash algorithm
try {
hashPasswordWithoutSalt("test".toCharArray());
DefaultEncryptionHandler.logger.info(MessageFormat
.format("Using non-salt hashing algorithm {0}", this.nonSaltAlgorithm));
DefaultEncryptionHandler.logger.info(
MessageFormat.format("Using non-salt hashing algorithm {0}", this.nonSaltAlgorithm));
} catch (Exception e) {
String msg = "[{0}] Defined parameter {1} is invalid because of underlying exception: {2}";
msg = MessageFormat.format(msg, EncryptionHandler.class.getName(), XML_PARAM_HASH_ALGORITHM_NON_SALT,
@ -183,12 +186,11 @@ public class DefaultEncryptionHandler implements EncryptionHandler {
// test hash algorithm
try {
hashPassword("test".toCharArray(), "test".getBytes());
DefaultEncryptionHandler.logger
.info(MessageFormat.format("Using hashing algorithm {0}", this.algorithm));
DefaultEncryptionHandler.logger.info(MessageFormat.format("Using hashing algorithm {0}", this.algorithm));
} catch (Exception e) {
String msg = "[{0}] Defined parameter {1} is invalid because of underlying exception: {2}";
msg = MessageFormat
.format(msg, EncryptionHandler.class.getName(), XML_PARAM_HASH_ALGORITHM, e.getLocalizedMessage());
msg = MessageFormat.format(msg, EncryptionHandler.class.getName(), XML_PARAM_HASH_ALGORITHM,
e.getLocalizedMessage());
throw new PrivilegeException(msg, e);
}
}

View File

@ -15,9 +15,20 @@
*/
package li.strolch.privilege.handler;
import static java.text.MessageFormat.format;
import static li.strolch.utils.helper.ExceptionHelper.getRootCause;
import static li.strolch.utils.helper.StringHelper.*;
import li.strolch.privilege.base.*;
import li.strolch.privilege.model.*;
import li.strolch.privilege.model.internal.*;
import li.strolch.privilege.policy.PrivilegePolicy;
import li.strolch.privilege.xml.CertificateStubsDomWriter;
import li.strolch.privilege.xml.CertificateStubsSaxReader;
import li.strolch.privilege.xml.CertificateStubsSaxReader.CertificateStub;
import li.strolch.utils.collections.Tuple;
import li.strolch.utils.concurrent.ElementLockingHandler;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.AesCryptoHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXParseException;
import javax.crypto.SecretKey;
import java.io.File;
@ -34,21 +45,9 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import li.strolch.privilege.base.*;
import li.strolch.privilege.model.*;
import li.strolch.privilege.model.internal.*;
import li.strolch.privilege.policy.PrivilegePolicy;
import li.strolch.privilege.xml.CertificateStubsDomWriter;
import li.strolch.privilege.xml.CertificateStubsSaxReader;
import li.strolch.privilege.xml.CertificateStubsSaxReader.CertificateStub;
import li.strolch.utils.collections.Tuple;
import li.strolch.utils.concurrent.ElementLockingHandler;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.AesCryptoHelper;
import li.strolch.utils.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXParseException;
import static java.text.MessageFormat.format;
import static li.strolch.utils.helper.ExceptionHelper.getRootCause;
import static li.strolch.utils.helper.StringHelper.*;
/**
* <p>
@ -418,24 +417,23 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
UserHistory history = new UserHistory();
byte[] passwordHash = null;
byte[] salt = null;
PasswordCrypt passwordCrypt = null;
if (password != null) {
// validate password meets basic requirements
validatePassword(certificate.getLocale(), password);
// get new salt for user
salt = this.encryptionHandler.nextSalt();
byte[] salt = this.encryptionHandler.nextSalt();
// hash password
passwordHash = this.encryptionHandler.hashPassword(password, salt);
passwordCrypt = this.encryptionHandler.hashPassword(password, salt);
history.setLastPasswordChange(ZonedDateTime.now());
}
// create new user
User newUser = createUser(userRep, history, passwordHash, salt, false);
User newUser = createUser(userRep, history, passwordCrypt, false);
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
@ -481,10 +479,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// add user
// make sure userId is not set
if (isNotEmpty(userRep.getUserId())) {
String msg = "UserId can not be set when adding a new user!";
throw new PrivilegeModelException(format(msg, userRep.getUsername()));
}
if (isNotEmpty(userRep.getUserId()))
throw new PrivilegeModelException("UserId can not be set when adding a new user!");
// set userId
userRep.setUserId(getUniqueId());
@ -495,7 +491,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
validateRolesExist(userRep);
// create new user
user = createUser(userRep, new UserHistory(), null, null, false);
user = createUser(userRep, new UserHistory(), null, false);
// detect privilege conflicts
assertNoPrivilegeConflict(user);
@ -514,9 +510,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
userRep.setUserId(existingUser.getUserId());
UserHistory history = existingUser.getHistory().getClone();
byte[] passwordHash = existingUser.getPassword();
byte[] salt = existingUser.getSalt();
user = createUser(userRep, history, passwordHash, salt, existingUser.isPasswordChangeRequested());
user = createUser(userRep, history, existingUser.getPasswordCrypt(),
existingUser.isPasswordChangeRequested());
// detect privilege conflicts
assertNoPrivilegeConflict(user);
@ -570,23 +565,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
UserHistory history = existingUser.getHistory().getClone();
byte[] passwordHash = null;
byte[] salt = null;
PasswordCrypt passwordCrypt = null;
if (password != null) {
// validate password meets basic requirements
validatePassword(certificate.getLocale(), password);
// get new salt for user
salt = this.encryptionHandler.nextSalt();
byte[] salt = this.encryptionHandler.nextSalt();
// hash password
passwordHash = this.encryptionHandler.hashPassword(password, salt);
passwordCrypt = this.encryptionHandler.hashPassword(password, salt);
history.setLastPasswordChange(ZonedDateTime.now());
}
User newUser = createUser(userRep, history, passwordHash, salt, existingUser.isPasswordChangeRequested());
User newUser = createUser(userRep, history, passwordCrypt, existingUser.isPasswordChangeRequested());
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
@ -617,7 +611,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
}
private User createUser(UserRep userRep, UserHistory history, byte[] passwordHash, byte[] salt,
private User createUser(UserRep userRep, UserHistory history, PasswordCrypt passwordCrypt,
boolean passwordChangeRequested) {
String userId = userRep.getUserId();
String userName = userRep.getUsername();
@ -627,9 +621,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
Set<String> roles = userRep.getRoles();
Locale locale = userRep.getLocale();
Map<String, String> properties = userRep.getProperties();
return new User(userId, userName, passwordHash, salt, this.encryptionHandler.getAlgorithm(),
this.encryptionHandler.getIterations(), this.encryptionHandler.getKeyLength(), firstName, lastName,
state, roles, locale, properties, passwordChangeRequested, history);
return new User(userId, userName, passwordCrypt, firstName, lastName, state, roles, locale, properties,
passwordChangeRequested, history);
}
@Override
@ -658,8 +651,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
String userId = existingUser.getUserId();
String username = existingUser.getUsername();
byte[] password = existingUser.getPassword();
byte[] salt = existingUser.getSalt();
PasswordCrypt passwordCrypt = existingUser.getPasswordCrypt();
String firstName = existingUser.getFirstname();
String lastName = existingUser.getLastname();
UserState userState = existingUser.getUserState();
@ -667,10 +659,6 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
Locale locale = existingUser.getLocale();
Map<String, String> propertyMap = existingUser.getProperties();
String hashAlgorithm = existingUser.getHashAlgorithm();
int hashIterations = existingUser.getHashIterations();
int hashKeyLength = existingUser.getHashKeyLength();
// get updated fields
if (isNotEmpty(userRep.getFirstname()))
firstName = userRep.getFirstname();
@ -687,9 +675,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
roles = userRep.getRoles();
// create new user
User newUser = new User(userId, username, password, salt, hashAlgorithm, hashIterations, hashKeyLength,
firstName, lastName, userState, roles, locale, propertyMap, existingUser.isPasswordChangeRequested(),
existingUser.getHistory().getClone());
User newUser = new User(userId, username, passwordCrypt, firstName, lastName, userState, roles, locale,
propertyMap, existingUser.isPasswordChangeRequested(), existingUser.getHistory().getClone());
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
@ -775,11 +762,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
Set<String> newRoles = new HashSet<>(currentRoles);
newRoles.add(roleName);
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), newRoles, existingUser.getLocale(), existingUser.getProperties(),
existingUser.isPasswordChangeRequested(), existingUser.getHistory().getClone());
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(),
existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles,
existingUser.getLocale(), existingUser.getProperties(), existingUser.isPasswordChangeRequested(),
existingUser.getHistory().getClone());
// detect privilege conflicts
assertNoPrivilegeConflict(newUser);
@ -826,11 +812,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
// create new user
Set<String> newRoles = new HashSet<>(currentRoles);
newRoles.remove(roleName);
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), newRoles, existingUser.getLocale(), existingUser.getProperties(),
existingUser.isPasswordChangeRequested(), existingUser.getHistory().getClone());
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(),
existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(), newRoles,
existingUser.getLocale(), existingUser.getProperties(), existingUser.isPasswordChangeRequested(),
existingUser.getHistory().getClone());
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
@ -861,11 +846,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
throw new PrivilegeModelException(format("User {0} does not exist!", username));
// create new user
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), existingUser.getRoles(), locale, existingUser.getProperties(),
existingUser.isPasswordChangeRequested(), existingUser.getHistory().getClone());
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(),
existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(),
existingUser.getRoles(), locale, existingUser.getProperties(), existingUser.isPasswordChangeRequested(),
existingUser.getHistory().getClone());
// if the user is not setting their own locale, then make sure this user may set this user's locale
if (!certificate.getUsername().equals(username)) {
@ -905,11 +889,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
throw new PrivilegeModelException(format("User {0} is remote and can not set password!", username));
// create new user
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), existingUser.getRoles(), existingUser.getLocale(),
existingUser.getProperties(), true, existingUser.getHistory().getClone());
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(),
existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(),
existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties(), true,
existingUser.getHistory().getClone());
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
@ -945,28 +928,25 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
UserHistory history = existingUser.getHistory().getClone();
byte[] passwordHash = null;
byte[] salt = null;
PasswordCrypt passwordCrypt = null;
if (password != null) {
// validate password meets basic requirements
validatePassword(certificate.getLocale(), password);
// get new salt for user
salt = this.encryptionHandler.nextSalt();
byte[] salt = this.encryptionHandler.nextSalt();
// hash password
passwordHash = this.encryptionHandler.hashPassword(password, salt);
passwordCrypt = this.encryptionHandler.hashPassword(password, salt);
history.setLastPasswordChange(ZonedDateTime.now());
}
// create new user
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), passwordHash, salt,
this.encryptionHandler.getAlgorithm(), this.encryptionHandler.getIterations(),
this.encryptionHandler.getKeyLength(), existingUser.getFirstname(), existingUser.getLastname(),
existingUser.getUserState(), existingUser.getRoles(), existingUser.getLocale(),
existingUser.getProperties(), false, history);
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), passwordCrypt,
existingUser.getFirstname(), existingUser.getLastname(), existingUser.getUserState(),
existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties(), false, history);
if (!certificate.getUsername().equals(username)) {
// check that the user may change their own password
@ -1014,11 +994,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
throw new PrivilegeModelException(format("User {0} does not exist!", username));
// create new user
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPassword(),
existingUser.getSalt(), existingUser.getHashAlgorithm(), existingUser.getHashIterations(),
existingUser.getHashKeyLength(), existingUser.getFirstname(), existingUser.getLastname(), state,
existingUser.getRoles(), existingUser.getLocale(), existingUser.getProperties(),
existingUser.isPasswordChangeRequested(), existingUser.getHistory().getClone());
User newUser = new User(existingUser.getUserId(), existingUser.getUsername(), existingUser.getPasswordCrypt(),
existingUser.getFirstname(), existingUser.getLastname(), state, existingUser.getRoles(),
existingUser.getLocale(), existingUser.getProperties(), existingUser.isPasswordChangeRequested(),
existingUser.getHistory().getClone());
// validate that this user may modify this user's state
prvCtx.validateAction(new SimpleRestrictable(PRIVILEGE_SET_USER_STATE, new Tuple(existingUser, newUser)));
@ -1727,7 +1706,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
throw new InvalidCredentialsException(msg);
}
// make sure not a system user - they may not login in
// make sure not a system user - they may not login
if (user.getUserState() == UserState.SYSTEM) {
String msg = "User {0} is a system user and may not login!";
msg = format(msg, username);
@ -1742,44 +1721,42 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
throw new AccessDeniedException(msg);
}
byte[] pwHash = user.getPassword();
if (pwHash == null)
PasswordCrypt userPasswordCrypt = user.getPasswordCrypt();
if (userPasswordCrypt.getPassword() == null)
throw new InvalidCredentialsException(format("User {0} has no password and may not login!", username));
byte[] salt = user.getSalt();
// we only work with hashed passwords
byte[] passwordHash;
if (salt == null) {
passwordHash = this.encryptionHandler.hashPasswordWithoutSalt(password);
} else if (user.getHashAlgorithm() == null || user.getHashIterations() == -1 || user.getHashKeyLength() == -1) {
passwordHash = this.encryptionHandler.hashPassword(password, salt);
PasswordCrypt requestPasswordCrypt;
if (userPasswordCrypt.getSalt() == null) {
requestPasswordCrypt = this.encryptionHandler.hashPasswordWithoutSalt(password);
} else if (userPasswordCrypt.getHashAlgorithm() == null || userPasswordCrypt.getHashIterations() == -1 ||
userPasswordCrypt.getHashKeyLength() == -1) {
requestPasswordCrypt = this.encryptionHandler.hashPassword(password, userPasswordCrypt.getSalt());
} else {
passwordHash = this.encryptionHandler.hashPassword(password, salt, user.getHashAlgorithm(),
user.getHashIterations(), user.getHashKeyLength());
requestPasswordCrypt = this.encryptionHandler.hashPassword(password, userPasswordCrypt.getSalt(),
userPasswordCrypt.getHashAlgorithm(), userPasswordCrypt.getHashIterations(),
userPasswordCrypt.getHashKeyLength());
}
// validate password
if (!Arrays.equals(passwordHash, pwHash))
if (!Arrays.equals(requestPasswordCrypt.getPassword(), userPasswordCrypt.getPassword()))
throw new InvalidCredentialsException(format("Password is incorrect for {0}", username));
// see if we need to update the hash
if (user.getHashAlgorithm() == null || user.getHashIterations() != this.encryptionHandler.getIterations() ||
user.getHashKeyLength() != this.encryptionHandler.getKeyLength()) {
if (this.encryptionHandler.isPasswordCryptOutdated(userPasswordCrypt)) {
logger.warn("Updating user " + username + " due to change in hashing algorithm properties ");
// get new salt for user
salt = this.encryptionHandler.nextSalt();
byte[] salt = this.encryptionHandler.nextSalt();
// hash password
passwordHash = this.encryptionHandler.hashPassword(password, salt);
PasswordCrypt newPasswordCrypt = this.encryptionHandler.hashPassword(password, salt);
// create new user
User newUser = new User(user.getUserId(), user.getUsername(), passwordHash, salt,
this.encryptionHandler.getAlgorithm(), this.encryptionHandler.getIterations(),
this.encryptionHandler.getKeyLength(), user.getFirstname(), user.getLastname(), user.getUserState(),
user.getRoles(), user.getLocale(), user.getProperties(), user.isPasswordChangeRequested(),
user.getHistory().getClone());
User newUser = new User(user.getUserId(), user.getUsername(), newPasswordCrypt, user.getFirstname(),
user.getLastname(), user.getUserState(), user.getRoles(), user.getLocale(), user.getProperties(),
user.isPasswordChangeRequested(), user.getHistory().getClone());
// delegate user replacement to persistence handler
this.persistenceHandler.replaceUser(newUser);
@ -2115,21 +2092,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
throw new PrivilegeModelException(msg);
}
File persistSessionsPath = new File(persistSessionsPathS);
if (!persistSessionsPath.getParentFile().isDirectory()) {
String msg = "Path for param {0} is invalid as parent does not exist or is not a directory. Value: {1}";
msg = format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPath.getAbsolutePath());
throw new PrivilegeModelException(msg);
}
if (persistSessionsPath.exists() && (!persistSessionsPath.isFile() || !persistSessionsPath.canWrite())) {
String msg
= "Path for param {0} is invalid as file exists but is not a file or not writeable. Value: {1}";
msg = format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPath.getAbsolutePath());
throw new PrivilegeModelException(msg);
}
this.persistSessionsPath = persistSessionsPath;
this.persistSessionsPath = getPersistSessionFile(persistSessionsPathS);
logger.info(format("Enabling persistence of sessions to {0}", this.persistSessionsPath.getAbsolutePath()));
} else {
String msg = "Parameter {0} has illegal value {1}. Overriding with {2}";
@ -2139,6 +2102,22 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
}
private static File getPersistSessionFile(String persistSessionsPathS) {
File persistSessionsPath = new File(persistSessionsPathS);
if (!persistSessionsPath.getParentFile().isDirectory()) {
String msg = "Path for param {0} is invalid as parent does not exist or is not a directory. Value: {1}";
msg = format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPath.getAbsolutePath());
throw new PrivilegeModelException(msg);
}
if (persistSessionsPath.exists() && (!persistSessionsPath.isFile() || !persistSessionsPath.canWrite())) {
String msg = "Path for param {0} is invalid as file exists but is not a file or not writeable. Value: {1}";
msg = format(msg, PARAM_PERSIST_SESSIONS_PATH, persistSessionsPath.getAbsolutePath());
throw new PrivilegeModelException(msg);
}
return persistSessionsPath;
}
private void handleConflictResolutionParam(Map<String, String> parameterMap) {
String privilegeConflictResolutionS = parameterMap.get(PARAM_PRIVILEGE_CONFLICT_RESOLUTION);
if (privilegeConflictResolutionS == null) {
@ -2163,14 +2142,14 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
String secretKeyS = parameterMap.get(PARAM_SECRET_KEY);
if (isEmpty(secretKeyS)) {
String msg = "Parameter {0} may not be empty";
msg = format(msg, PARAM_SECRET_KEY, PARAM_PRIVILEGE_CONFLICT_RESOLUTION);
msg = format(msg, PARAM_SECRET_KEY);
throw new PrivilegeModelException(msg);
}
String secretSaltS = parameterMap.get(PARAM_SECRET_SALT);
if (isEmpty(secretSaltS)) {
String msg = "Parameter {0} may not be empty";
msg = format(msg, PARAM_SECRET_SALT, PARAM_PRIVILEGE_CONFLICT_RESOLUTION);
msg = format(msg, PARAM_SECRET_SALT);
throw new PrivilegeModelException(msg);
}
@ -2367,8 +2346,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler {
}
// validate password
byte[] pwHash = user.getPassword();
if (pwHash != null) {
if (user.getPasswordCrypt() != null) {
String msg = format("System users must not have a password: {0}", user.getUsername());
throw new AccessDeniedException(msg);
}

View File

@ -15,9 +15,9 @@
*/
package li.strolch.privilege.handler;
import java.util.Map;
import li.strolch.privilege.model.internal.PasswordCrypt;
import li.strolch.privilege.helper.Crypt;
import java.util.Map;
/**
* The {@link EncryptionHandler} exposes API which is used to handle encrypting of strings, or returning secure tokens
@ -27,13 +27,6 @@ import li.strolch.privilege.helper.Crypt;
*/
public interface EncryptionHandler {
/**
* Returns a new crypt instance
*
* @return a new crypt instance
*/
Crypt newCryptInstance();
/**
* Returns the configured algorithm
*
@ -72,49 +65,43 @@ public interface EncryptionHandler {
/**
* Hashes the given password configured algorithm
*
* @param password
* the password
* @param password the password
*
* @return the hashed password
* @return the {@link PasswordCrypt}
*/
byte[] hashPasswordWithoutSalt(final char[] password);
PasswordCrypt hashPasswordWithoutSalt(final char[] password);
/**
* Hashes the given password with the given salt with the configured algorithm
*
* @param password
* the password
* @param salt
* the salt
* @param password the password
* @param salt the salt
*
* @return the hashed password
* @return the {@link PasswordCrypt}
*/
byte[] hashPassword(final char[] password, final byte[] salt);
PasswordCrypt hashPassword(final char[] password, final byte[] salt);
/**
* Hashes the given password with the given salt and algorithm properties
*
* @param password
* the password
* @param salt
* the salt
* @param algorithm
* the algorithm
* @param iterations
* the iterations
* @param keyLength
* the keyLength
* @param password the password
* @param salt the salt
* @param algorithm the algorithm
* @param iterations the iterations
* @param keyLength the keyLength
*
* @return the hashed password
* @return the {@link PasswordCrypt}
*/
byte[] hashPassword(final char[] password, final byte[] salt, String algorithm, int iterations, int keyLength);
PasswordCrypt hashPassword(final char[] password, final byte[] salt, String algorithm, int iterations,
int keyLength);
boolean isPasswordCryptOutdated(PasswordCrypt userPasswordCrypt);
/**
* Initialize the concrete {@link EncryptionHandler}. The passed parameter map contains any configuration the
* concrete {@link EncryptionHandler} might need
*
* @param parameterMap
* a map containing configuration properties
* @param parameterMap a map containing configuration properties
*/
void initialize(Map<String, String> parameterMap);

View File

@ -1,212 +0,0 @@
package li.strolch.privilege.helper;
import static li.strolch.privilege.base.PrivilegeConstants.DEFAULT_SMALL_ITERATIONS;
import static li.strolch.utils.helper.StringHelper.fromHexString;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import li.strolch.utils.dbc.DBC;
import li.strolch.utils.helper.StringHelper;
public class Crypt {
private String algorithm;
private int keyLength;
private int iterations;
private byte[] salt;
private byte[] password;
public Crypt() {
// nothing to do
}
public String getAlgorithm() {
return algorithm;
}
public Crypt setAlgorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
public byte[] getSalt() {
return salt;
}
public Crypt setSalt(byte[] salt) {
this.salt = salt;
return this;
}
public int getKeyLength() {
return this.keyLength;
}
public Crypt setKeyLength(int keyLength) {
this.keyLength = keyLength;
return this;
}
public int getIterations() {
return this.iterations;
}
public Crypt setIterations(int iterations) {
this.iterations = iterations;
return this;
}
public byte[] getPassword() {
return password;
}
public Crypt setPassword(byte[] password) {
this.password = password;
return this;
}
public Crypt parseCrypt(String crypt) {
DBC.PRE.assertNotEmpty("crypt can no be empty", crypt);
if (crypt.contains("$")) {
String[] parts = crypt.split("\\$");
if (parts.length == 5) {
setAlgorithm(parts[1], true);
Map<String, String> algOptions = parseAlgOptions(parts[2]);
if (!algOptions.containsKey("rounds"))
this.iterations = DEFAULT_SMALL_ITERATIONS;
else
this.iterations = Integer.parseInt(algOptions.get("rounds"));
this.salt = fromHexString(parts[3]);
this.password = fromHexString(parts[4]);
} else if (parts.length == 4) {
setAlgorithm(parts[1], true);
this.iterations = DEFAULT_SMALL_ITERATIONS;
this.salt = fromHexString(parts[2]);
this.password = fromHexString(parts[3]);
} else if (parts.length == 3) {
setAlgorithm(parts[1], false);
this.password = fromHexString(parts[2]);
} else {
throw new IllegalStateException("Wrong number of $ chars in " + crypt + ": " + parts.length);
}
} else {
this.algorithm = "SHA-512";
this.password = fromHexString(crypt);
}
return this;
}
public void assertSame(char[] password) {
if (!isSame(password))
throw new IllegalStateException("Passwords not the same");
}
public boolean isSame(char[] password) {
if (this.password == null)
throw new IllegalStateException("password not set, call parseCrypt() first!");
if (password == null)
throw new IllegalStateException("password must not be null");
try {
byte[] hash;
if (this.salt == null) {
hash = StringHelper.hash(this.algorithm, new String(password).getBytes());
} else {
PBEKeySpec spec = new PBEKeySpec(password, this.salt, this.iterations, this.keyLength);
SecretKeyFactory skf = SecretKeyFactory.getInstance(this.algorithm);
SecretKey key = skf.generateSecret(spec);
hash = key.getEncoded();
}
return Arrays.equals(hash, this.password);
} catch (Exception e) {
throw new IllegalStateException("Failed validation password for algorithm " + this.algorithm, e);
}
}
public String toCryptString() {
StringBuilder sb = new StringBuilder();
sb.append("$");
switch (this.algorithm) {
case "MD5" -> sb.append("1");
case "PBKDF2WithHmacSHA256", "SHA-256" -> sb.append("5");
case "PBKDF2WithHmacSHA512", "SHA-512" -> sb.append("6");
default -> throw new IllegalStateException("Unhandled algorithm " + this.algorithm);
}
if (this.iterations != 0 && this.iterations != DEFAULT_SMALL_ITERATIONS) {
sb.append("$");
sb.append("rounds");
sb.append("=");
sb.append(iterations);
}
if (this.salt != null) {
sb.append("$");
sb.append(StringHelper.toHexString(this.salt));
}
sb.append("$");
sb.append(StringHelper.toHexString(this.password));
return sb.toString();
}
private Map<String, String> parseAlgOptions(String part) {
String[] options = part.split(",");
Map<String, String> algOptions = new HashMap<>(options.length);
for (String option : options) {
if (option.trim().isEmpty())
continue;
if (!option.contains("="))
throw new IllegalStateException("Option " + option + " is missing = char");
String[] keyValue = option.split("=");
algOptions.put(keyValue[0].trim(), keyValue[1].trim());
}
return algOptions;
}
private void setAlgorithm(String id, boolean hasSalt) {
switch (id) {
case "1" -> {
this.algorithm = "MD5";
this.keyLength = 0;
}
case "5" -> {
this.algorithm = hasSalt ? "PBKDF2WithHmacSHA256" : "SHA-256";
this.keyLength = 256;
}
case "6" -> {
this.algorithm = hasSalt ? "PBKDF2WithHmacSHA512" : "SHA-512";
this.keyLength = 256;
}
default -> throw new IllegalStateException("Unhandled ID " + id);
}
}
}

View File

@ -1,20 +0,0 @@
package li.strolch.privilege.helper;
import li.strolch.privilege.model.internal.User;
import li.strolch.utils.helper.StringHelper;
public class CryptHelper {
public static String buildPasswordString(User user) {
return buildPasswordString(user.getHashAlgorithm(), user.getHashIterations(), user.getHashKeyLength(),
user.getSalt(), user.getPassword());
}
public static String buildPasswordString(String hashAlgorithm, int hashIterations, int hashKeyLength, byte[] salt,
byte[] passwordArr) {
String algo = hashAlgorithm + "," + hashIterations + "," + hashKeyLength;
String hash = StringHelper.toHexString(salt);
String password = StringHelper.toHexString(passwordArr);
return "$" + algo + "$" + hash + "$" + password;
}
}

View File

@ -15,8 +15,9 @@
*/
package li.strolch.privilege.helper;
import static li.strolch.privilege.base.PrivilegeConstants.*;
import static li.strolch.privilege.helper.CryptHelper.buildPasswordString;
import li.strolch.privilege.handler.DefaultEncryptionHandler;
import li.strolch.privilege.model.internal.PasswordCrypt;
import li.strolch.utils.helper.StringHelper;
import javax.crypto.SecretKeyFactory;
import java.io.BufferedReader;
@ -24,8 +25,8 @@ import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import li.strolch.privilege.handler.DefaultEncryptionHandler;
import li.strolch.utils.helper.StringHelper;
import static li.strolch.privilege.base.PrivilegeConstants.*;
import static li.strolch.privilege.model.internal.PasswordCrypt.buildPasswordString;
/**
* <p>
@ -37,11 +38,9 @@ import li.strolch.utils.helper.StringHelper;
public class PasswordCreator {
/**
* @param args
* the args from the command line, NOT USED
* @param args the args from the command line, NOT USED
*
* @throws Exception
* thrown if anything goes wrong
* @throws Exception thrown if anything goes wrong
*/
public static void main(String[] args) throws Exception {
@ -136,18 +135,16 @@ public class PasswordCreator {
}
byte[] salt = saltS.getBytes();
byte[] passwordHash = encryptionHandler.hashPassword(password, salt);
String passwordHashS = StringHelper.toHexString(passwordHash);
PasswordCrypt passwordCrypt = encryptionHandler.hashPassword(password, salt);
String passwordHashS = StringHelper.toHexString(passwordCrypt.getPassword());
System.out.println("Hash is: " + passwordHashS);
System.out.println("Salt is: " + saltS);
System.out.println();
System.out.println(
XmlConstants.XML_ATTR_PASSWORD + "=\"" + passwordHashS + "\" " + XmlConstants.XML_ATTR_SALT + "=\""
+ saltS + "\"");
System.out.println(
XmlConstants.XML_ATTR_PASSWORD + "=\"" + buildPasswordString(hashAlgorithm, iterations, keyLength,
salt, passwordHash) + "\"");
XmlConstants.XML_ATTR_PASSWORD + "=\"" + passwordHashS + "\" " + XmlConstants.XML_ATTR_SALT +
"=\"" + saltS + "\"");
System.out.println(XmlConstants.XML_ATTR_PASSWORD + "=\"" + passwordCrypt.buildPasswordString() + "\"");
System.out.println();
}
}

View File

@ -0,0 +1,120 @@
package li.strolch.privilege.model.internal;
import li.strolch.privilege.helper.XmlConstants;
import li.strolch.utils.helper.StringHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static li.strolch.utils.helper.StringHelper.*;
public class PasswordCrypt {
private final byte[] password;
private final byte[] salt;
private final String hashAlgorithm;
private final int hashIterations;
private final int hashKeyLength;
private static final Logger logger = LoggerFactory.getLogger(PasswordCrypt.class);
public PasswordCrypt(byte[] password, byte[] salt) {
this.password = password;
this.salt = salt;
this.hashAlgorithm = null;
this.hashIterations = -1;
this.hashKeyLength = -1;
}
public PasswordCrypt(byte[] password, byte[] salt, String hashAlgorithm, int hashIterations, int hashKeyLength) {
this.password = password;
this.salt = salt;
this.hashAlgorithm = hashAlgorithm;
this.hashIterations = hashIterations;
this.hashKeyLength = hashKeyLength;
}
public byte[] getPassword() {
return password;
}
public byte[] getSalt() {
return salt;
}
public String getHashAlgorithm() {
return hashAlgorithm;
}
public int getHashIterations() {
return hashIterations;
}
public int getHashKeyLength() {
return hashKeyLength;
}
public static PasswordCrypt parse(String passwordS, String saltS) {
if (isEmpty(passwordS))
return null;
byte[] salt = null;
if (isNotEmpty(saltS))
salt = fromHexString(saltS.trim());
if (isEmpty(passwordS))
return new PasswordCrypt(null, salt);
passwordS = passwordS.trim();
byte[] password;
if (!passwordS.startsWith("$")) {
password = fromHexString(passwordS);
return new PasswordCrypt(password, salt);
}
String[] parts = passwordS.split("\\$");
if (parts.length != 4)
throw new IllegalArgumentException(
"Illegal password " + passwordS + ": Starts with $, but does not have 3 parts!");
String hashAlgorithmS = parts[1];
String[] hashParts = hashAlgorithmS.split(",");
if (hashParts.length != 3)
throw new IllegalArgumentException(
"Illegal password " + passwordS + ": hashAlgorithm part does not have 3 parts separated by comma!");
String hashAlgorithm = hashParts[0];
int hashIterations = Integer.parseInt(hashParts[1]);
int hashKeyLength = Integer.parseInt(hashParts[2]);
salt = fromHexString(parts[2]);
password = fromHexString(parts[3]);
return new PasswordCrypt(password, salt, hashAlgorithm, hashIterations, hashKeyLength);
}
@Override
public String toString() {
return buildPasswordString();
}
public String buildPasswordString() {
if (this.password == null || this.salt == null || this.hashAlgorithm == null || this.hashIterations == -1 ||
this.hashKeyLength == -1) {
return null;
}
return buildPasswordString(getHashAlgorithm(), getHashIterations(), getHashKeyLength(), getSalt(),
getPassword());
}
public static String buildPasswordString(String hashAlgorithm, int hashIterations, int hashKeyLength, byte[] salt,
byte[] passwordArr) {
String algo = hashAlgorithm + "," + hashIterations + "," + hashKeyLength;
String hash = toHexString(salt);
String password = toHexString(passwordArr);
return "$" + algo + "$" + hash + "$" + password;
}
}

View File

@ -15,16 +15,16 @@
*/
package li.strolch.privilege.model.internal;
import static li.strolch.privilege.base.PrivilegeConstants.*;
import java.util.*;
import li.strolch.privilege.base.PrivilegeConstants;
import li.strolch.privilege.base.PrivilegeException;
import li.strolch.privilege.model.UserRep;
import li.strolch.privilege.model.UserState;
import li.strolch.utils.helper.StringHelper;
import java.util.*;
import static li.strolch.privilege.base.PrivilegeConstants.*;
/**
* This class defines the actual login information for a given user which can be granted privileges. Every user is
* granted a set of {@link Role}s and has a {@link UserState} including detail information like first name and lastname
@ -41,11 +41,7 @@ public final class User {
private final String userId;
private final String username;
private final byte[] password;
private final byte[] salt;
private final String hashAlgorithm;
private final int hashIterations;
private final int hashKeyLength;
private final PasswordCrypt passwordCrypt;
private final String firstname;
private final String lastname;
@ -62,36 +58,19 @@ public final class User {
/**
* Default constructor
*
* @param userId
* the user's id
* @param username
* the user's login name
* @param password
* the user's password (hashed)
* @param salt
* the password salt
* @param hashAlgorithm
* the algorithm for the hash
* @param hashIterations
* the nr of iterations for hashing
* @param hashKeyLength
* the hash key length
* @param firstname
* the user's first name
* @param lastname
* the user's lastname
* @param userState
* the user's {@link UserState}
* @param roles
* the set of {@link Role}s assigned to this user
* @param locale
* the user's {@link Locale}
* @param propertyMap
* a {@link Map} containing string value pairs of properties for this user
* @param userId the user's id
* @param username the user's login name
* @param passwordCrypt the {@link PasswordCrypt} containing user's password information
* @param firstname the user's first name
* @param lastname the user's lastname
* @param userState the user's {@link UserState}
* @param roles the set of {@link Role}s assigned to this user
* @param locale the user's {@link Locale}
* @param propertyMap a {@link Map} containing string value pairs of properties for this user
*/
public User(String userId, String username, byte[] password, byte[] salt, String hashAlgorithm, int hashIterations,
int hashKeyLength, String firstname, String lastname, UserState userState, Set<String> roles, Locale locale,
Map<String, String> propertyMap, boolean passwordChangeRequested, UserHistory history) {
public User(String userId, String username, PasswordCrypt passwordCrypt, String firstname, String lastname,
UserState userState, Set<String> roles, Locale locale, Map<String, String> propertyMap,
boolean passwordChangeRequested, UserHistory history) {
if (StringHelper.isEmpty(userId))
throw new PrivilegeException("No UserId defined!");
@ -109,7 +88,7 @@ public final class User {
if (history == null)
throw new PrivilegeException("History must not be null!");
// password, salt and hash* may be null, meaning not able to login
// passwordCrypt may be null, meaning not able to login
// roles may be null, meaning not able to login and must be added later
// locale may be null, meaning use system default
// properties may be null, meaning no properties
@ -117,12 +96,7 @@ public final class User {
this.userId = userId;
this.username = username;
this.password = password;
this.salt = salt;
this.hashAlgorithm = hashAlgorithm;
this.hashIterations = hashIterations;
this.hashKeyLength = hashKeyLength;
this.passwordCrypt = passwordCrypt;
this.userState = userState;
@ -163,48 +137,12 @@ public final class User {
}
/**
* Returns the hashed password for this {@link User}
* Returns the {@link PasswordCrypt} for this user, null if not password set
*
* @return the hashed password for this {@link User}
* @return the {@link PasswordCrypt} for this user, null if not password set
*/
public byte[] getPassword() {
return this.password;
}
/**
* Return the salt for this {@link User}
*
* @return the salt for this {@link User}
*/
public byte[] getSalt() {
return this.salt;
}
/**
* Return the hash algorithm
*
* @return the hash algorithm
*/
public String getHashAlgorithm() {
return this.hashAlgorithm;
}
/**
* Return the hashIterations
*
* @return hashIterations
*/
public int getHashIterations() {
return this.hashIterations;
}
/**
* Return the hashKeyLength
*
* @return hashKeyLength
*/
public int getHashKeyLength() {
return this.hashKeyLength;
public PasswordCrypt getPasswordCrypt() {
return this.passwordCrypt;
}
/**
@ -238,8 +176,7 @@ public final class User {
/**
* Returns true if this user has the specified role
*
* @param role
* the name of the {@link Role} to check for
* @param role the name of the {@link Role} to check for
*
* @return true if the this user has the specified role
*/
@ -279,8 +216,7 @@ public final class User {
/**
* Returns the property with the given key
*
* @param key
* the key for which the property is to be returned
* @param key the key for which the property is to be returned
*
* @return the property with the given key, or null if the property is not defined
*/
@ -357,9 +293,9 @@ public final class User {
*/
@Override
public String toString() {
return "User [userId=" + this.userId + ", username=" + this.username + ", firstname=" + this.firstname
+ ", lastname=" + this.lastname + ", locale=" + this.locale + ", userState=" + this.userState
+ ", roles=" + this.roles + "]";
return "User [userId=" + this.userId + ", username=" + this.username + ", firstname=" + this.firstname +
", lastname=" + this.lastname + ", locale=" + this.locale + ", userState=" + this.userState +
", roles=" + this.roles + "]";
}
@Override

View File

@ -15,14 +15,8 @@
*/
package li.strolch.privilege.xml;
import static java.util.Comparator.comparing;
import static li.strolch.privilege.helper.CryptHelper.buildPasswordString;
import java.io.File;
import java.util.List;
import java.util.Map;
import li.strolch.privilege.helper.XmlConstants;
import li.strolch.privilege.model.internal.PasswordCrypt;
import li.strolch.privilege.model.internal.User;
import li.strolch.privilege.model.internal.UserHistory;
import li.strolch.utils.helper.StringHelper;
@ -31,6 +25,15 @@ import li.strolch.utils.iso8601.ISO8601;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.File;
import java.util.List;
import java.util.Map;
import static java.util.Comparator.comparing;
import static li.strolch.privilege.helper.XmlConstants.*;
import static li.strolch.privilege.model.internal.PasswordCrypt.buildPasswordString;
import static li.strolch.utils.helper.StringHelper.*;
/**
* @author Robert von Burg <eitch@eitchnet.ch>
*/
@ -50,90 +53,90 @@ public class PrivilegeUsersDomWriter {
// create document root
Document doc = XmlHelper.createDocument();
Element rootElement = doc.createElement(XmlConstants.XML_USERS);
Element rootElement = doc.createElement(XML_USERS);
doc.appendChild(rootElement);
this.users.forEach(user -> {
// create the user element
Element userElement = doc.createElement(XmlConstants.XML_USER);
Element userElement = doc.createElement(XML_USER);
rootElement.appendChild(userElement);
userElement.setAttribute(XmlConstants.XML_ATTR_USER_ID, user.getUserId());
userElement.setAttribute(XmlConstants.XML_ATTR_USERNAME, user.getUsername());
userElement.setAttribute(XML_ATTR_USER_ID, user.getUserId());
userElement.setAttribute(XML_ATTR_USERNAME, user.getUsername());
writePassword(user, userElement);
// add first name element
if (StringHelper.isNotEmpty(user.getFirstname())) {
Element firstnameElement = doc.createElement(XmlConstants.XML_FIRSTNAME);
if (isNotEmpty(user.getFirstname())) {
Element firstnameElement = doc.createElement(XML_FIRSTNAME);
firstnameElement.setTextContent(user.getFirstname());
userElement.appendChild(firstnameElement);
}
// add last name element
if (StringHelper.isNotEmpty(user.getLastname())) {
Element lastnameElement = doc.createElement(XmlConstants.XML_LASTNAME);
if (isNotEmpty(user.getLastname())) {
Element lastnameElement = doc.createElement(XML_LASTNAME);
lastnameElement.setTextContent(user.getLastname());
userElement.appendChild(lastnameElement);
}
// add state element
Element stateElement = doc.createElement(XmlConstants.XML_STATE);
Element stateElement = doc.createElement(XML_STATE);
stateElement.setTextContent(user.getUserState().toString());
userElement.appendChild(stateElement);
// add locale element
Element localeElement = doc.createElement(XmlConstants.XML_LOCALE);
Element localeElement = doc.createElement(XML_LOCALE);
localeElement.setTextContent(user.getLocale().toLanguageTag());
userElement.appendChild(localeElement);
// add password change requested element
if (user.isPasswordChangeRequested()) {
Element passwordChangeRequestedElement = doc.createElement(XmlConstants.XML_PASSWORD_CHANGE_REQUESTED);
Element passwordChangeRequestedElement = doc.createElement(XML_PASSWORD_CHANGE_REQUESTED);
passwordChangeRequestedElement.setTextContent(Boolean.toString(true));
userElement.appendChild(passwordChangeRequestedElement);
}
// add all the role elements
Element rolesElement = doc.createElement(XmlConstants.XML_ROLES);
Element rolesElement = doc.createElement(XML_ROLES);
userElement.appendChild(rolesElement);
user.getRoles().stream().sorted().forEach(roleName -> {
Element roleElement = doc.createElement(XmlConstants.XML_ROLE);
Element roleElement = doc.createElement(XML_ROLE);
roleElement.setTextContent(roleName);
rolesElement.appendChild(roleElement);
});
// add the parameters
if (!user.getProperties().isEmpty()) {
Element parametersElement = doc.createElement(XmlConstants.XML_PROPERTIES);
Element parametersElement = doc.createElement(XML_PROPERTIES);
userElement.appendChild(parametersElement);
user.getProperties().entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> {
Element paramElement = doc.createElement(XmlConstants.XML_PROPERTY);
paramElement.setAttribute(XmlConstants.XML_ATTR_NAME, entry.getKey());
paramElement.setAttribute(XmlConstants.XML_ATTR_VALUE, entry.getValue());
Element paramElement = doc.createElement(XML_PROPERTY);
paramElement.setAttribute(XML_ATTR_NAME, entry.getKey());
paramElement.setAttribute(XML_ATTR_VALUE, entry.getValue());
parametersElement.appendChild(paramElement);
});
}
if (!user.isHistoryEmpty()) {
UserHistory history = user.getHistory();
Element historyElement = doc.createElement(XmlConstants.XML_HISTORY);
Element historyElement = doc.createElement(XML_HISTORY);
userElement.appendChild(historyElement);
if (!history.isFirstLoginEmpty()) {
Element element = doc.createElement(XmlConstants.XML_FIRST_LOGIN);
Element element = doc.createElement(XML_FIRST_LOGIN);
element.setTextContent(ISO8601.toString(history.getFirstLogin()));
historyElement.appendChild(element);
}
if (!history.isLastLoginEmpty()) {
Element element = doc.createElement(XmlConstants.XML_LAST_LOGIN);
Element element = doc.createElement(XML_LAST_LOGIN);
element.setTextContent(ISO8601.toString(history.getLastLogin()));
historyElement.appendChild(element);
}
if (!history.isLastPasswordChangeEmpty()) {
Element element = doc.createElement(XmlConstants.XML_LAST_PASSWORD_CHANGE);
Element element = doc.createElement(XML_LAST_PASSWORD_CHANGE);
element.setTextContent(ISO8601.toString(history.getLastPasswordChange()));
historyElement.appendChild(element);
}
@ -145,14 +148,15 @@ public class PrivilegeUsersDomWriter {
}
private void writePassword(User user, Element userElement) {
if (user.getPassword() != null && user.getSalt() != null && user.getHashAlgorithm() != null
&& user.getHashIterations() != -1 && user.getHashKeyLength() != -1) {
userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, buildPasswordString(user));
PasswordCrypt passwordCrypt = user.getPasswordCrypt();
String passwordString = passwordCrypt.buildPasswordString();
if (passwordString != null) {
userElement.setAttribute(XML_ATTR_PASSWORD, passwordString);
} else {
if (user.getPassword() != null)
userElement.setAttribute(XmlConstants.XML_ATTR_PASSWORD, StringHelper.toHexString(user.getPassword()));
if (user.getSalt() != null)
userElement.setAttribute(XmlConstants.XML_ATTR_SALT, StringHelper.toHexString(user.getSalt()));
if (passwordCrypt.getPassword() != null)
userElement.setAttribute(XML_ATTR_PASSWORD, toHexString(passwordCrypt.getPassword()));
if (passwordCrypt.getSalt() != null)
userElement.setAttribute(XML_ATTR_SALT, toHexString(passwordCrypt.getSalt()));
}
}
}

View File

@ -21,6 +21,7 @@ import java.text.MessageFormat;
import java.util.*;
import li.strolch.privilege.model.UserState;
import li.strolch.privilege.model.internal.PasswordCrypt;
import li.strolch.privilege.model.internal.User;
import li.strolch.privilege.model.internal.UserHistory;
import li.strolch.utils.helper.StringHelper;
@ -116,11 +117,7 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
String userId;
String username;
byte[] password;
byte[] salt;
String hashAlgorithm;
int hashIterations = -1;
int hashKeyLength = -1;
PasswordCrypt passwordCrypt;
String firstName;
String lastname;
UserState userState;
@ -145,50 +142,12 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
String password = attributes.getValue(XML_ATTR_PASSWORD);
String salt = attributes.getValue(XML_ATTR_SALT);
parsePassword(password, salt);
this.passwordCrypt = PasswordCrypt.parse(password, salt);
} else if (qName.equals(XML_HISTORY)) {
this.history = new UserHistory();
}
}
private void parsePassword(String passwordS, String salt) {
if (StringHelper.isNotEmpty(salt))
this.salt = StringHelper.fromHexString(salt.trim());
if (StringHelper.isEmpty(passwordS))
return;
passwordS = passwordS.trim();
if (!passwordS.startsWith("$")) {
this.password = StringHelper.fromHexString(passwordS);
} else {
String[] parts = passwordS.split("\\$");
if (parts.length != 4) {
logger.error("Illegal password " + passwordS + ": Starts with $, but does not have 3 parts!");
} else {
String hashAlgorithm = parts[1];
String[] hashParts = hashAlgorithm.split(",");
if (hashParts.length != 3) {
logger.error("Illegal password " + passwordS
+ ": hashAlgorithm part does not have 3 parts separated by comma!");
} else {
this.hashAlgorithm = hashParts[0];
this.hashIterations = Integer.parseInt(hashParts[1]);
this.hashKeyLength = Integer.parseInt(hashParts[2]);
this.salt = StringHelper.fromHexString(parts[2]);
this.password = StringHelper.fromHexString(parts[3]);
}
}
}
}
@Override
public void characters(char[] ch, int start, int length) {
this.text.append(ch, start, length);
@ -198,37 +157,37 @@ public class PrivilegeUsersSaxReader extends DefaultHandler {
public void endElement(String uri, String localName, String qName) {
switch (qName) {
case XML_FIRSTNAME -> this.firstName = this.text.toString().trim();
case XML_LASTNAME -> this.lastname = this.text.toString().trim();
case XML_STATE -> this.userState = UserState.valueOf(this.text.toString().trim());
case XML_LOCALE -> this.locale = Locale.forLanguageTag(this.text.toString().trim());
case XML_PASSWORD_CHANGE_REQUESTED ->
this.passwordChangeRequested = Boolean.parseBoolean(this.text.toString().trim());
case XML_FIRST_LOGIN -> this.history.setFirstLogin(ISO8601.parseToZdt(this.text.toString().trim()));
case XML_LAST_LOGIN -> this.history.setLastLogin(ISO8601.parseToZdt(this.text.toString().trim()));
case XML_LAST_PASSWORD_CHANGE ->
this.history.setLastPasswordChange(ISO8601.parseToZdt(this.text.toString().trim()));
case XML_ROLE -> this.userRoles.add(this.text.toString().trim());
case XML_USER -> {
if (this.history == null)
this.history = new UserHistory();
case XML_FIRSTNAME -> this.firstName = this.text.toString().trim();
case XML_LASTNAME -> this.lastname = this.text.toString().trim();
case XML_STATE -> this.userState = UserState.valueOf(this.text.toString().trim());
case XML_LOCALE -> this.locale = Locale.forLanguageTag(this.text.toString().trim());
case XML_PASSWORD_CHANGE_REQUESTED ->
this.passwordChangeRequested = Boolean.parseBoolean(this.text.toString().trim());
case XML_FIRST_LOGIN -> this.history.setFirstLogin(ISO8601.parseToZdt(this.text.toString().trim()));
case XML_LAST_LOGIN -> this.history.setLastLogin(ISO8601.parseToZdt(this.text.toString().trim()));
case XML_LAST_PASSWORD_CHANGE ->
this.history.setLastPasswordChange(ISO8601.parseToZdt(this.text.toString().trim()));
case XML_ROLE -> this.userRoles.add(this.text.toString().trim());
case XML_USER -> {
if (this.history == null)
this.history = new UserHistory();
User user = new User(this.userId, this.username, this.password, this.salt, this.hashAlgorithm,
hashIterations, hashKeyLength, this.firstName, this.lastname, this.userState, this.userRoles,
this.locale, this.parameters, this.passwordChangeRequested, this.history);
User user = new User(this.userId, this.username, this.passwordCrypt, this.firstName, this.lastname,
this.userState, this.userRoles, this.locale, this.parameters, this.passwordChangeRequested,
this.history);
logger.info(MessageFormat.format("New User: {0}", user));
String username = caseInsensitiveUsername ? user.getUsername().toLowerCase() : user.getUsername();
users.put(username, user);
}
default -> {
if (!(qName.equals(XML_ROLES) //
|| qName.equals(XML_PARAMETER) //
|| qName.equals(XML_HISTORY) //
|| qName.equals(XML_PARAMETERS))) {
throw new IllegalArgumentException("Unhandled tag " + qName);
logger.info(MessageFormat.format("New User: {0}", user));
String username = caseInsensitiveUsername ? user.getUsername().toLowerCase() : user.getUsername();
users.put(username, user);
}
default -> {
if (!(qName.equals(XML_ROLES) //
|| qName.equals(XML_PARAMETER) //
|| qName.equals(XML_HISTORY) //
|| qName.equals(XML_PARAMETERS))) {
throw new IllegalArgumentException("Unhandled tag " + qName);
}
}
}
}
}

View File

@ -1,16 +1,14 @@
package li.strolch.privilege.test;
import static li.strolch.privilege.base.PrivilegeConstants.*;
import static org.junit.Assert.assertEquals;
import li.strolch.privilege.handler.DefaultEncryptionHandler;
import li.strolch.privilege.helper.XmlConstants;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import li.strolch.privilege.handler.DefaultEncryptionHandler;
import li.strolch.privilege.helper.Crypt;
import li.strolch.privilege.helper.XmlConstants;
import org.junit.BeforeClass;
import org.junit.Test;
import static li.strolch.privilege.base.PrivilegeConstants.*;
public class CryptTest {
@ -27,74 +25,28 @@ public class CryptTest {
encryptionHandler.initialize(parameterMap);
}
@Test
public void shouldAssertSamePassword01() {
String hash = "$1$21232f297a57a5a743894a0e4a801fc3";
char[] password = "admin".toCharArray();
Crypt crypt = new Crypt().parseCrypt(hash);
crypt.assertSame(password);
assertEquals(hash, crypt.toCryptString());
}
@Test
public void shouldAssertSamePassword05() {
String hash = "$5$8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918";
char[] password = "admin".toCharArray();
Crypt crypt = new Crypt().parseCrypt(hash);
crypt.assertSame(password);
assertEquals(hash, crypt.toCryptString());
}
@Test
public void shouldAssertSamePassword06() {
String hash = "$6$c7ad44cbad762a5da0a452f9e854fdc1e0e7a52a38015f23f3eab1d80b931dd472634dfac71cd34ebc35d16ab7fb8a90c81f975113d6c7538dc69dd8de9077ec";
char[] password = "admin".toCharArray();
Crypt crypt = new Crypt().parseCrypt(hash);
crypt.assertSame(password);
assertEquals(hash, crypt.toCryptString());
}
@Test
public void shouldAssertSamePassword15() {
String hash = "$5$61646d696e$f4aec2c20dd0c3ff0547f4bd56facd76097cce7c613da80c67842b6a357fdc04";
char[] password = "admin".toCharArray();
Crypt crypt = new Crypt().parseCrypt(hash);
crypt.assertSame(password);
assertEquals(hash, crypt.toCryptString());
}
@Test
public void shouldAssertSamePassword16() {
String hash = "$6$rounds=5000$61646d696e$5a39ca7443147f9bf549ee0c2d5ded0640690ed56ef8c903e1b0da2a3339010b";
char[] password = "admin".toCharArray();
Crypt crypt = new Crypt().parseCrypt(hash);
crypt.assertSame(password);
assertEquals(hash, crypt.toCryptString());
}
@Test
public void shouldAssertSamePassword20() {
char[] password = "admin".toCharArray();
byte[] salt = "admin".getBytes();
byte[] passwordHash = encryptionHandler.hashPassword(password, salt);
Crypt crypt = encryptionHandler.newCryptInstance();
crypt.setSalt(salt);
crypt.setPassword(passwordHash);
String hash = "$6$61646d696e$cb69962946617da006a2f95776d78b49e5ec7941d2bdb2d25cdb05f957f64344";
assertEquals(hash, crypt.toCryptString());
// char[] password = "admin".toCharArray();
//
// byte[] salt = "admin".getBytes();
// byte[] passwordHash = encryptionHandler.hashPassword(password, salt);
//
// Crypt crypt = encryptionHandler.newCryptInstance();
// crypt.setSalt(salt);
// crypt.setPassword(passwordHash);
//
//
// encryptionHandler.
// String hash = "$PBKDF2WithHmacSHA512,100000,256$943f2d9208079322e50297f018c44d77d4e887e07bc9b37b2b80d121ad7dbd6e$8bcd819c99e79975e93e5f8bd87a376737afacd4427d7b33f0f69d0fc8030da5";
//
// requestPasswordCrypt = encryptionHandler.hashPassword(password, userPasswordCrypt.getSalt(),
// userPasswordCrypt.getHashAlgorithm(), userPasswordCrypt.getHashIterations(),
// userPasswordCrypt.getHashKeyLength());
//
// // validate password
// if (!Arrays.equals(requestPasswordCrypt.getPassword(), userPasswordCrypt.getPassword()))
// throw new InvalidCredentialsException(format("Password is incorrect for {0}", username));
}
}