X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=opendaylight%2Fusermanager%2Fapi%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fusermanager%2FUserConfig.java;h=83532a1012f6a9431cd4f75a3f1b201e653ec395;hb=9212fed678702583f4a555641208cf1c7b45b829;hp=07c814adf14c7b2d35f9991a9bd3e5875296f922;hpb=ae69cd76093e444e0b8f5b3ca10302af36024c86;p=controller.git diff --git a/opendaylight/usermanager/api/src/main/java/org/opendaylight/controller/usermanager/UserConfig.java b/opendaylight/usermanager/api/src/main/java/org/opendaylight/controller/usermanager/UserConfig.java index 07c814adf1..83532a1012 100644 --- a/opendaylight/usermanager/api/src/main/java/org/opendaylight/controller/usermanager/UserConfig.java +++ b/opendaylight/usermanager/api/src/main/java/org/opendaylight/controller/usermanager/UserConfig.java @@ -9,46 +9,86 @@ package org.opendaylight.controller.usermanager; import java.io.Serializable; -import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + import org.opendaylight.controller.sal.authorization.AuthResultEnum; +import org.opendaylight.controller.sal.packet.BitBufferHelper; import org.opendaylight.controller.sal.utils.HexEncode; import org.opendaylight.controller.sal.utils.Status; import org.opendaylight.controller.sal.utils.StatusCode; -import org.opendaylight.controller.usermanager.AuthResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Configuration Java Object which represents a Local AAA user configuration * information for User Manager. */ +@XmlRootElement +@XmlAccessorType(XmlAccessType.NONE) public class UserConfig implements Serializable { private static final long serialVersionUID = 1L; - - protected String user; - protected List roles; - private String password; - + private static Logger log = LoggerFactory.getLogger(UserConfig.class); private static final boolean strongPasswordCheck = Boolean.getBoolean("enableStrongPasswordCheck"); + private static final String DIGEST_ALGORITHM = "SHA-384"; private static final String BAD_PASSWORD = "Bad Password"; private static final int USERNAME_MAXLENGTH = 32; - protected static final String PASSWORD_REGEX = "(?=.*[^\\w])(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,256}$"; + protected static final String PASSWORD_REGEX = "(?=.*[^a-zA-Z0-9])(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,256}$"; private static final Pattern INVALID_USERNAME_CHARACTERS = Pattern.compile("([/\\s\\.\\?#%;\\\\]+)"); - private static MessageDigest oneWayFunction = null; + private static MessageDigest oneWayFunction; + private static SecureRandom randomGenerator; + static { try { - UserConfig.oneWayFunction = MessageDigest.getInstance("SHA-1"); + UserConfig.oneWayFunction = MessageDigest.getInstance(DIGEST_ALGORITHM); } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); + log.error(String.format("Implementation of %s digest algorithm not found: %s", DIGEST_ALGORITHM, + e.getMessage())); } + UserConfig.randomGenerator = new SecureRandom(BitBufferHelper.toByteArray(System.currentTimeMillis())); } + /** + * User Id + */ + @XmlElement + protected String user; + + /** + * List of roles a user can have + * example + * System-Admin + * Network-Admin + * Network-Operator + */ + @XmlElement + protected List roles; + + /** + * Password + * Should be 8 to 256 characters long, + * contain both upper and lower case letters, at least one number, + * and at least one non alphanumeric character. + */ + @XmlElement + private String password; + + private byte[] salt; + + + public UserConfig() { } @@ -68,11 +108,19 @@ public class UserConfig implements Serializable { /* * Password validation to be done on clear text password. If fails, mark * the password with a well known label, so that object validation can - * report the proper error. Only if password is a valid one, hash it. + * report the proper error. Only if password is a valid one, generate + * salt, concatenate it with clear text password and hash the + * resulting string. Hash result is going to be our stored password. */ - this.password = (validatePassword(password).isSuccess()) ? hash(password) : BAD_PASSWORD; + if (validateClearTextPassword(password).isSuccess()) { + this.salt = BitBufferHelper.toByteArray(randomGenerator.nextLong()); + this.password = hash(salt, password); + } else { + this.salt = null; + this.password = BAD_PASSWORD; + } - this.roles = (roles == null) ? new ArrayList() : new ArrayList(roles); + this.roles = (roles == null) ? Collections.emptyList() : new ArrayList(roles); } public String getUser() { @@ -142,6 +190,7 @@ public class UserConfig implements Serializable { public Status validate() { Status validCheck = validateUsername(); if (validCheck.isSuccess()) { + // Password validation was run at object construction time validCheck = (!password.equals(BAD_PASSWORD)) ? new Status(StatusCode.SUCCESS) : new Status( StatusCode.BADREQUEST, "Password should be 8 to 256 characters long, contain both upper and lower case letters, " @@ -169,7 +218,7 @@ public class UserConfig implements Serializable { return new Status(StatusCode.SUCCESS); } - private Status validatePassword(String password) { + private Status validateClearTextPassword(String password) { if (password == null || password.isEmpty()) { return new Status(StatusCode.BADREQUEST, "Password cannot be empty"); } @@ -193,14 +242,14 @@ public class UserConfig implements Serializable { // To make any changes to a user configured profile, current password // must always be provided - if (!this.password.equals(hash(currentPassword))) { + if (!this.password.equals(hash(this.salt, currentPassword))) { return new Status(StatusCode.BADREQUEST, "Current password is incorrect"); } // Create a new object with the proposed modifications UserConfig proposed = new UserConfig(); proposed.user = this.user; - proposed.password = (newPassword == null)? this.password : hash(newPassword); + proposed.password = (newPassword == null)? this.password : hash(this.salt, newPassword); proposed.roles = (newRoles == null)? this.roles : newRoles; // Validate it @@ -217,9 +266,9 @@ public class UserConfig implements Serializable { return status; } - public AuthResponse authenticate(String clearTextPass) { + public AuthResponse authenticate(String clearTextPassword) { AuthResponse locResponse = new AuthResponse(); - if (password.equals(hash(clearTextPass))) { + if (password.equals(hash(this.salt, clearTextPassword))) { locResponse.setStatus(AuthResultEnum.AUTH_ACCEPT_LOC); locResponse.addData(getRolesString()); } else { @@ -241,12 +290,32 @@ public class UserConfig implements Serializable { return buffer.toString(); } - public static String hash(String message) { + private static byte[] concatenate(byte[] salt, String password) { + byte[] messageArray = password.getBytes(); + byte[] concatenation = new byte[salt.length + password.length()]; + System.arraycopy(salt, 0, concatenation, 0, salt.length); + System.arraycopy(messageArray, 0, concatenation, salt.length, messageArray.length); + return concatenation; + } + + private static String hash(byte[] salt, String message) { if (message == null) { + log.warn("Password hash requested but empty or no password provided"); return message; } + if (salt == null || salt.length == 0) { + log.warn("Password hash requested but empty or no salt provided"); + return message; + } + + // Concatenate salt and password + byte[] messageArray = message.getBytes(); + byte[] concatenation = new byte[salt.length + message.length()]; + System.arraycopy(salt, 0, concatenation, 0, salt.length); + System.arraycopy(messageArray, 0, concatenation, salt.length, messageArray.length); + UserConfig.oneWayFunction.reset(); - return HexEncode.bytesToHexString(UserConfig.oneWayFunction.digest(message.getBytes(Charset.defaultCharset()))); + return HexEncode.bytesToHexString(UserConfig.oneWayFunction.digest(concatenate(salt, message))); } /** @@ -265,7 +334,8 @@ public class UserConfig implements Serializable { public static UserConfig getUncheckedUserConfig(String userName, String password, List roles) { UserConfig config = new UserConfig(); config.user = userName; - config.password = hash(password); + config.salt = BitBufferHelper.toByteArray(randomGenerator.nextLong()); + config.password = hash(config.salt, password); config.roles = roles; return config; }