Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,26 @@ ohmage depends on a set of directories to store log files and user data. By defa

Any Servlet 3.0 compliant container should work. Internally, we use Tomcat. To build the WAR file, use `ant clean dist`, which will produce an ssl-disabled container. It should be noted that we do not recommend having the servlet itself handle SSL, and instead suggest you use a web server like nginx or apache to do SSL termination.

## Using SHA-512 for Password Hashing

The Blowfish algorithm is used by default for password hashing. Ohmage supports SHA512 algorithm for password hashing.

To use SHA-512, run the following commands in MySQL console as soon as ohmage is up and running:
```
--Enables SHA-512 password hashing
UPDATE `ohmage`.`preference` SET `p_value` = 'true' WHERE `p_key` = 'sha512_password_hash_enabled';
--Updates the default SHA512 password hash for `ohmage.admin` user to `ohmage.passwd`
UPDATE `ohmage`.`user` SET `password` = '$6$Afmg23YTsd$113jh7VsD6q6wDnDWD9SqJUzobqjFIuGMhpgpuXM49acjyjFfWOGAhzT7W7zRleIhN2Xe.xH7ki2bk8nBlsX4/' WHERE `username` = 'ohmage.admin';
```

To use blowfish, run the following commands:
```
--Disables SHA-512 password hashing (thus enabling default blowfish)
UPDATE `ohmage`.`preference` SET `p_value` = 'false' WHERE `p_key` = 'sha512_password_hash_enabled';
--Updates the default blowfish password hash for `ohmage.admin` user to `ohmage.passwd`
UPDATE `ohmage`.`user` SET `password` = '$2a$13$yxus2tQ3/QiOwWcELImOQuy9d5PXWbByQ6Bhp52b1se7fNYGFxN5i' WHERE `username` = 'ohmage.admin';
```

# Collaboration

The coding rules are loose, and the best reference would be other parts of the code. A few rules we do have are:
Expand Down
4 changes: 3 additions & 1 deletion build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<property name="condition" location="condition"/>

<fileset id="compile-dependencies" dir="${library}">
<include name="commons-codec-1.10.jar" />
<include name="log4j-1.2.16.jar"/>
<include name="servlet-api-3.0.jar"/>
<include name="org.springframework.beans-3.1.1.RELEASE.jar" />
Expand Down Expand Up @@ -45,6 +46,7 @@
</fileset>

<fileset id="run-dependencies" dir="${library}">
<include name="commons-codec-1.10.jar" />
<include name="log4j-1.2.16.jar"/>
<include name="org.springframework.asm-3.1.1.RELEASE.jar" />
<include name="org.springframework.beans-3.1.1.RELEASE.jar" />
Expand Down Expand Up @@ -125,7 +127,7 @@
<classpath refid="compile-classpath"/>
</javac>
</target>

<target name="javac-library" description="Compiles Java files to create the ohmage library.">
<mkdir dir="${build}/classes"/>
<javac destdir="${build}/classes" source="1.7" target="1.7"
Expand Down
9 changes: 9 additions & 0 deletions db/migration/V11__sha512_pw_hashing_support.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Adds configuration option for enabling sha512 password hashing
-- with default value of false

INSERT INTO preference (p_key, p_value) VALUES
('sha512_password_hash_enabled', 'false')
ON DUPLICATE KEY UPDATE p_value=VALUES(p_value);

-- Changes password column length to 120 chars to accomadate sha512 password hashes
ALTER TABLE `user` MODIFY `password` VARCHAR(120);
Binary file added lib/commons-codec-1.10.jar
Binary file not shown.
5 changes: 5 additions & 0 deletions src/org/ohmage/cache/PreferenceCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ public final class PreferenceCache extends KeyValueCache {

// Local auth enabling.
public static final String KEY_LOCAL_AUTH_ENABLED = "local_auth_enabled";

// SHA512 hash enabling (over the default blowfish option -
// useful for redhat based distros).
public static final String KEY_SHA512_PASSWORD_HASH_ENABLED =
"sha512_password_hash_enabled";

// The reference to one's self to return to requesters.
private static PreferenceCache instance;
Expand Down
15 changes: 15 additions & 0 deletions src/org/ohmage/domain/ServerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public class ServerConfig {
public static final boolean DEFAULT_SELF_REGISTRATION_ALLOWED = true;
public static final boolean DEFAULT_KEYCLOAK_AUTH_ENABLED = false;
public static final boolean DEFAULT_LOCAL_AUTH_ENABLED = true;
public static final boolean DEFAULT_SHA512_PASSWORD_HASH_ENABLED = false;


private final String appName;
Expand All @@ -119,6 +120,7 @@ public class ServerConfig {
private final boolean userSetupEnabled;
private final boolean keycloakAuthEnabled;
private final boolean localAuthEnabled;
private final boolean sha512PasswordHashEnabled;
private final String publicClassId;

/**
Expand Down Expand Up @@ -154,6 +156,7 @@ public ServerConfig(
final boolean userSetupEnabled,
final boolean keycloakAuthEnabled,
final boolean localAuthEnabled,
final boolean sha512PasswordHashEnabled,
final String publicClassId)
throws DomainException {

Expand Down Expand Up @@ -203,6 +206,7 @@ else if(StringUtils.isEmptyOrWhitespaceOnly(publicClassId)){

this.keycloakAuthEnabled = keycloakAuthEnabled;
this.localAuthEnabled = localAuthEnabled;
this.sha512PasswordHashEnabled = sha512PasswordHashEnabled;

this.publicClassId = publicClassId;
}
Expand Down Expand Up @@ -392,6 +396,8 @@ public ServerConfig(
"The public class id was missing from the JSON.",
e);
}

sha512PasswordHashEnabled = DEFAULT_SHA512_PASSWORD_HASH_ENABLED;

}

Expand Down Expand Up @@ -522,6 +528,15 @@ public final boolean getKeycloakAuthEnabled() {
public final boolean getLocalAuthEnabled() {
return localAuthEnabled;
}

/**
* Returns whether or not sha512 password hashing is enabled.
*
* @return Whether or not sha512 password hashing is enabled.
*/
public final boolean getSha512PasswordHashingEnabled() {
return sha512PasswordHashEnabled;
}

/**
* Returns the public class id of the server.
Expand Down
28 changes: 23 additions & 5 deletions src/org/ohmage/query/impl/AuthenticationQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,22 @@

import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.log4j.Logger;

import javax.sql.DataSource;

import jbcrypt.BCrypt;
import org.apache.commons.codec.digest.Crypt;

import org.ohmage.annotator.Annotator.ErrorCode;
import org.ohmage.domain.KeycloakUser;
import org.ohmage.domain.User;
import org.ohmage.exception.DataAccessException;
import org.ohmage.exception.ServiceException;
import org.ohmage.query.IAuthenticationQuery;
import org.ohmage.request.UserRequest;
import org.ohmage.service.ConfigServices;
import org.ohmage.service.UserServices;
import org.springframework.jdbc.core.RowMapper;

/**
Expand All @@ -36,6 +41,8 @@
* @author John Jenkins
*/
public final class AuthenticationQuery extends Query implements IAuthenticationQuery{
private static final Logger LOGGER = Logger.getLogger("UserServices");

// Gets the user's hashed password from the database.
private static final String SQL_GET_PASSWORD =
"SELECT password " +
Expand Down Expand Up @@ -105,7 +112,7 @@ public boolean getNewAccount() {
private AuthenticationQuery(DataSource dataSource) {
super(dataSource);

instance = this;
instance = this;
}

/* (non-Javadoc)
Expand All @@ -115,7 +122,7 @@ private AuthenticationQuery(DataSource dataSource) {
public UserInformation execute(UserRequest userRequest) throws DataAccessException {
User user = userRequest.getUser();
String hashedPassword;
// Hash the password if necessary.
// SN: set password to fixed string if user is a keycloak user
if(user instanceof KeycloakUser){
Expand All @@ -137,10 +144,21 @@ else if(user.hashPassword()) {
if(actualPassword.equals(KeycloakUser.KEYCLOAK_USER_PASSWORD)){
userRequest.setFailed(ErrorCode.AUTHENTICATION_FAILED, "Unknown user or incorrect password.");
return null;
}
hashedPassword = BCrypt.hashpw(user.getPassword(), actualPassword);
}

if( ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled() ) {
hashedPassword = Crypt.crypt(user.getPassword(), actualPassword);
}
else {
hashedPassword = BCrypt.hashpw(user.getPassword(), actualPassword);
}

userRequest.getUser().setHashedPassword(hashedPassword);
}
catch (ServiceException e) {
userRequest.setFailed(ErrorCode.AUTHENTICATION_FAILED, "Unexpected error occurred.");
return null;
}
catch(org.springframework.dao.IncorrectResultSizeDataAccessException e) {
// If there were multiple users with the same username,
if(e.getActualSize() > 1) {
Expand All @@ -160,7 +178,7 @@ else if(user.hashPassword()) {
else {
hashedPassword = user.getPassword();
}

// Get the user's information from the database.
try {
UserInformation userInformation = instance.getJdbcTemplate().queryForObject(
Expand Down
20 changes: 20 additions & 0 deletions src/org/ohmage/service/ConfigServices.java
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,25 @@ public static ServerConfig readServerConfiguration()
catch(IllegalArgumentException e) {
throw new ServiceException("The local auth config was not a valid boolean.", e);
}

boolean sha512PasswordHashEnabled;
try {
sha512PasswordHashEnabled =
StringUtils.decodeBoolean(
PreferenceCache.instance().lookup(
PreferenceCache.KEY_SHA512_PASSWORD_HASH_ENABLED));
}
catch(CacheMissException e) {
sha512PasswordHashEnabled =
ServerConfig.DEFAULT_SHA512_PASSWORD_HASH_ENABLED;
LOGGER.warn("sha512_password_hash_enabled config is "
+ "missing from the DB. Will default to "
+ sha512PasswordHashEnabled);
}
catch(IllegalArgumentException e) {
throw new ServiceException("The sha512_password_hash_enabled "
+ " was not a valid boolean.", e);
}

String publicClassId;
try {
Expand All @@ -200,6 +219,7 @@ public static ServerConfig readServerConfiguration()
userSetupEnabled,
keycloakAuthEnabled,
localAuthEnabled,
sha512PasswordHashEnabled,
publicClassId);
}
catch(DomainException e) {
Expand Down
77 changes: 56 additions & 21 deletions src/org/ohmage/service/UserServices.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
import javax.mail.internet.MimeMessage;
import com.sun.mail.smtp.SMTPTransport;

import org.apache.commons.codec.digest.Crypt;
import org.ohmage.service.ConfigServices;

import jbcrypt.BCrypt;
import net.tanesha.recaptcha.ReCaptchaImpl;
import net.tanesha.recaptcha.ReCaptchaResponse;
Expand Down Expand Up @@ -121,7 +124,7 @@ public final class UserServices {
private IUserClassQueries userClassQueries;
private IUserImageQueries userImageQueries;
private IImageQueries imageQueries;

/**
* Default constructor. Privately instantiated via dependency injection
* (reflection).
Expand Down Expand Up @@ -161,7 +164,7 @@ private UserServices(IUserQueries iUserQueries,
userCampaignQueries = iUserCampaignQueries;
userClassQueries = iUserClassQueries;
userImageQueries = iUserImageQueries;
imageQueries = iImageQueries;
imageQueries = iImageQueries;
}

/**
Expand All @@ -170,7 +173,7 @@ private UserServices(IUserQueries iUserQueries,
public static UserServices instance() {
return instance;
}

/**
* Creates a new user.
*
Expand Down Expand Up @@ -211,8 +214,15 @@ public void createUser(
hashedPassword =
KeycloakUser.KEYCLOAK_USER_PASSWORD;
} else {
hashedPassword =
BCrypt.hashpw(password, BCrypt.gensalt(User.BCRYPT_COMPLEXITY));
if( ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled() ) {
hashedPassword =
Crypt.crypt(password);
}
else {
hashedPassword =
BCrypt.hashpw(password, BCrypt.gensalt(User.BCRYPT_COMPLEXITY));
}

}

userQueries
Expand Down Expand Up @@ -276,8 +286,14 @@ public boolean createUser(
hashedPassword =
KeycloakUser.KEYCLOAK_USER_PASSWORD;
} else {
hashedPassword =
BCrypt.hashpw(password, BCrypt.gensalt(User.BCRYPT_COMPLEXITY));
if(ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled()) {
hashedPassword =
Crypt.crypt(password);
}
else {
hashedPassword =
BCrypt.hashpw(password, BCrypt.gensalt(User.BCRYPT_COMPLEXITY));
}
}

return
Expand Down Expand Up @@ -338,11 +354,17 @@ public void createUserRegistration(
}
String registrationId = buffer.toString();


// Hash the password.
String hashedPassword =
BCrypt.hashpw(
password,
BCrypt.gensalt(User.BCRYPT_COMPLEXITY));
String hashedPassword;
if(ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled()) {
hashedPassword =
Crypt.crypt(password);
}
else {
hashedPassword =
BCrypt.hashpw(password, BCrypt.gensalt(User.BCRYPT_COMPLEXITY));
}

// Create the user in the database with all of its connections.
userQueries.createUserRegistration(
Expand Down Expand Up @@ -2039,9 +2061,18 @@ public void resetPassword(final String username)
}

try {
String hashedPassword;
if(ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled()) {
hashedPassword =
Crypt.crypt(newPassword);
}
else {
hashedPassword =
BCrypt.hashpw(newPassword, BCrypt.gensalt(User.BCRYPT_COMPLEXITY));
}
userQueries.updateUserPassword(
username,
BCrypt.hashpw(newPassword, BCrypt.gensalt(13)),
hashedPassword,
true);
}
catch(DataAccessException e) {
Expand Down Expand Up @@ -2194,15 +2225,19 @@ public String updatePassword(final String username,
final String plaintextPassword) throws ServiceException {

try {
String hashedPassword =
BCrypt
.hashpw(
plaintextPassword,
BCrypt.gensalt(User.BCRYPT_COMPLEXITY));

userQueries.updateUserPassword(username, hashedPassword, false);

return hashedPassword;
String hashedPassword;
if(ConfigServices.readServerConfiguration().getSha512PasswordHashingEnabled()) {
hashedPassword =
Crypt.crypt(plaintextPassword);
}
else {
hashedPassword =
BCrypt.hashpw(plaintextPassword, BCrypt.gensalt(User.BCRYPT_COMPLEXITY));
}

userQueries.updateUserPassword(username, hashedPassword, false);

return hashedPassword;
}
catch(DataAccessException e) {
throw new ServiceException(e);
Expand Down