Commit 44826566 authored by Richard Midwinter's avatar Richard Midwinter Committed by Dave Cridland

OF-631: Implement SCRAM support

This implements the SCRAM-SHA1 mechanism, and includes extending the existing
DefaultAuthProvider to store the Scram hashes for faster authentication.

If user.scramHashedOnly is set to true, then only these non-reversable hashes
are stored (and thus security is increased in exchanged for removing support
for DIGEST-MD5 et al).
parent 5e3f4161
......@@ -3,6 +3,10 @@
CREATE TABLE ofUser (
username VARCHAR(64) NOT NULL,
storedKey VARCHAR(32),
serverKey VARCHAR(32),
salt VARCHAR(32),
iterations INTEGER,
plainPassword VARCHAR(32),
encryptedPassword VARCHAR(255),
name VARCHAR(100),
......
......@@ -3,6 +3,10 @@
CREATE TABLE ofUser (
username VARCHAR(64) NOT NULL,
storedKey VARCHAR(32),
serverKey VARCHAR(32),
salt VARCHAR(32),
iterations INTEGER,
plainPassword VARCHAR(32),
encryptedPassword VARCHAR(255),
name VARCHAR(100),
......
......@@ -3,6 +3,10 @@
CREATE TABLE ofUser (
username VARCHAR(64) NOT NULL,
storedKey VARCHAR(32),
serverKey VARCHAR(32),
salt VARCHAR(32),
iterations INTEGER,
plainPassword VARCHAR(32),
encryptedPassword VARCHAR(255),
name VARCHAR(100),
......
......@@ -3,6 +3,10 @@
CREATE TABLE ofUser (
username VARCHAR2(64) NOT NULL,
storedKey VARCHAR(32),
serverKey VARCHAR(32),
salt VARCHAR(32),
iterations INTEGER,
plainPassword VARCHAR2(32),
encryptedPassword VARCHAR2(255),
name VARCHAR2(100),
......
......@@ -5,6 +5,10 @@
CREATE TABLE ofUser (
username VARCHAR(64) NOT NULL,
storedKey VARCHAR(32),
serverKey VARCHAR(32),
salt VARCHAR(32),
iterations INTEGER,
plainPassword VARCHAR(32),
encryptedPassword VARCHAR(255),
name VARCHAR(100),
......
......@@ -3,6 +3,10 @@
CREATE TABLE ofUser (
username NVARCHAR(64) NOT NULL,
storedKey VARCHAR(32),
serverKey VARCHAR(32),
salt VARCHAR(32),
iterations INTEGER,
plainPassword NVARCHAR(32),
encryptedPassword NVARCHAR(255),
name NVARCHAR(100),
......
......@@ -3,6 +3,10 @@
CREATE TABLE ofUser (
username NVARCHAR(64) NOT NULL,
storedKey VARCHAR(32),
serverKey VARCHAR(32),
salt VARCHAR(32),
iterations INTEGER,
plainPassword NVARCHAR(32) NULL,
encryptedPassword NVARCHAR(255) NULL,
name NVARCHAR(100) NULL,
......
// add columns for SASL SCRAM-SHA-1
ALTER TABLE ofUser ADD COLUMN storedKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN serverKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN salt VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN iterations INTEGER;
UPDATE ofVersion SET version = 22 WHERE name = 'openfire';
// add columns for SASL SCRAM-SHA-1
ALTER TABLE ofUser ADD COLUMN storedKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN serverKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN salt VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN iterations INTEGER;
UPDATE ofVersion SET version = 22 WHERE name = 'openfire';
// add columns for SASL SCRAM-SHA-1
ALTER TABLE ofUser ADD COLUMN storedKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN serverKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN salt VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN iterations INTEGER;
UPDATE ofVersion SET version = 22 WHERE name = 'openfire';
// add columns for SASL SCRAM-SHA-1
ALTER TABLE ofUser ADD COLUMN storedKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN serverKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN salt VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN iterations INTEGER;
UPDATE ofVersion SET version = 22 WHERE name = 'openfire';
COMMIT;
// add columns for SASL SCRAM-SHA-1
ALTER TABLE ofUser ADD COLUMN storedKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN serverKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN salt VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN iterations INTEGER;
UPDATE ofVersion SET version = 22 WHERE name = 'openfire';
// add columns for SASL SCRAM-SHA-1
ALTER TABLE ofUser ADD COLUMN storedKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN serverKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN salt VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN iterations INTEGER;
UPDATE ofVersion SET version = 22 WHERE name = 'openfire';
// add columns for SASL SCRAM-SHA-1
ALTER TABLE ofUser ADD COLUMN storedKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN serverKey VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN salt VARCHAR(32);
ALTER TABLE ofUser ADD COLUMN iterations INTEGER;
UPDATE ofVersion SET version = 22 WHERE name = 'openfire';
......@@ -1959,6 +1959,9 @@ setup.profile.description=Choose the user and group system to use with the serve
setup.profile.default=Default
setup.profile.default_description=Store users and groups in the server database. This is the \
best option for simple deployments.
setup.profile.default.scramOnly=Only Hashed Passwords
setup.profile.default.scramOnly_description=Store only non-reversible hashes of passwords in the database. \
This only supports PLAIN and SCRAM-SHA-1 capable clients.
setup.profile.ldap=Directory Server (LDAP)
setup.profile.ldap_description=Integrate with a directory server such as Active Directory or \
OpenLDAP using the LDAP protocol. Users and groups are stored in the directory and treated \
......
......@@ -68,7 +68,7 @@ public class SchemaManager {
/**
* Current Openfire database schema version.
*/
private static final int DATABASE_VERSION = 21;
private static final int DATABASE_VERSION = 22;
/**
* Creates a new Schema manager.
......
......@@ -49,6 +49,7 @@ import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.admin.AdminManager;
import org.jivesoftware.openfire.audit.AuditManager;
import org.jivesoftware.openfire.audit.spi.AuditManagerImpl;
import org.jivesoftware.openfire.auth.ScramUtils;
import org.jivesoftware.openfire.clearspace.ClearspaceManager;
import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.cluster.NodeID;
......@@ -421,6 +422,9 @@ public class XMPPServer {
}
}
// Set default SASL SCRAM-SHA-1 iteration count
JiveGlobals.setProperty("sasl.scram-sha-1.iteration-count", Integer.toString(ScramUtils.DEFAULT_ITERATION_COUNT));
// Update certificates (if required)
try {
// Check if keystore already has certificates for current domain
......
......@@ -328,4 +328,9 @@ public class AuthFactory {
}
return cipher;
}
public static boolean supportsScram() {
// TODO Auto-generated method stub
return authProvider.isScramSupported();
}
}
\ No newline at end of file
......@@ -129,4 +129,6 @@ public interface AuthProvider {
* backend user store.
*/
public boolean supportsPasswordRetrieval();
boolean isScramSupported();
}
\ No newline at end of file
......@@ -20,12 +20,19 @@
package org.jivesoftware.openfire.auth;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import javax.security.sasl.SaslException;
import javax.xml.bind.DatatypeConverter;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.user.UserNotFoundException;
......@@ -48,8 +55,12 @@ public class DefaultAuthProvider implements AuthProvider {
private static final String LOAD_PASSWORD =
"SELECT plainPassword,encryptedPassword FROM ofUser WHERE username=?";
private static final String TEST_PASSWORD =
"SELECT plainPassword,encryptedPassword,iterations,salt,storedKey FROM ofUser WHERE username=?";
private static final String UPDATE_PASSWORD =
"UPDATE ofUser SET plainPassword=?, encryptedPassword=? WHERE username=?";
"UPDATE ofUser SET plainPassword=?, encryptedPassword=?, storedKey=?, serverKey=?, salt=?, iterations=? WHERE username=?";
private static final SecureRandom random = new SecureRandom();
/**
* Constructs a new DefaultAuthProvider.
......@@ -75,7 +86,7 @@ public class DefaultAuthProvider implements AuthProvider {
}
}
try {
if (!password.equals(getPassword(username))) {
if (!checkPassword(username, password)) {
throw new UnauthorizedException();
}
}
......@@ -119,7 +130,8 @@ public class DefaultAuthProvider implements AuthProvider {
}
public boolean isDigestSupported() {
return true;
boolean scramOnly = JiveGlobals.getBooleanProperty("user.scramHashedPasswordOnly");
return !scramOnly;
}
public String getPassword(String username) throws UserNotFoundException {
......@@ -159,6 +171,9 @@ public class DefaultAuthProvider implements AuthProvider {
// Ignore and return plain password instead.
}
}
if (plainText == null) {
throw new UnsupportedOperationException();
}
return plainText;
}
catch (SQLException sqle) {
......@@ -169,9 +184,80 @@ public class DefaultAuthProvider implements AuthProvider {
}
}
public boolean checkPassword(String username, String testPassword) throws UserNotFoundException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
if (username.contains("@")) {
// Check that the specified domain matches the server's domain
int index = username.indexOf("@");
String domain = username.substring(index + 1);
if (domain.equals(XMPPServer.getInstance().getServerInfo().getXMPPDomain())) {
username = username.substring(0, index);
} else {
// Unknown domain.
throw new UserNotFoundException();
}
}
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(TEST_PASSWORD);
pstmt.setString(1, username);
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new UserNotFoundException(username);
}
String plainText = rs.getString(1);
String encrypted = rs.getString(2);
int iterations = rs.getInt(3);
String salt = rs.getString(4);
String storedKey = rs.getString(5);
if (encrypted != null) {
try {
plainText = AuthFactory.decryptPassword(encrypted);
}
catch (UnsupportedOperationException uoe) {
// Ignore and return plain password instead.
}
}
if (plainText != null) {
boolean scramOnly = JiveGlobals.getBooleanProperty("user.scramHashedPasswordOnly");
if (scramOnly) {
// If we have a password here, but we're meant to be scramOnly, we should reset it.
setPassword(username, plainText);
}
return testPassword.equals(plainText);
}
// Don't have either plain or encrypted, so test SCRAM hash.
if (salt == null || iterations == 0 || storedKey == null) {
Log.warn("No available credentials for checkPassword.");
return false;
}
byte[] saltShaker = DatatypeConverter.parseBase64Binary(salt);
byte[] saltedPassword = null, clientKey = null, testStoredKey = null;
try {
saltedPassword = ScramUtils.createSaltedPassword(saltShaker, testPassword, iterations);
clientKey = ScramUtils.computeHmac(saltedPassword, "Client Key");
testStoredKey = MessageDigest.getInstance("SHA-1").digest(clientKey);
} catch(SaslException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
Log.warn("Unable to check SCRAM values for PLAIN authentication.");
return false;
}
return DatatypeConverter.printBase64Binary(testStoredKey).equals(storedKey);
}
catch (SQLException sqle) {
Log.error("User SQL failure:", sqle);
throw new UserNotFoundException(sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
public void setPassword(String username, String password) throws UserNotFoundException {
// Determine if the password should be stored as plain text or encrypted.
boolean usePlainPassword = JiveGlobals.getBooleanProperty("user.usePlainPassword");
boolean scramOnly = JiveGlobals.getBooleanProperty("user.scramHashedPasswordOnly");
String encryptedPassword = null;
if (username.contains("@")) {
// Check that the specified domain matches the server's domain
......@@ -184,7 +270,26 @@ public class DefaultAuthProvider implements AuthProvider {
throw new UserNotFoundException();
}
}
if (!usePlainPassword) {
// Store the salt and salted password so SCRAM-SHA-1 SASL auth can be used later.
byte[] saltShaker = new byte[32];
random.nextBytes(saltShaker);
String salt = DatatypeConverter.printBase64Binary(saltShaker);
int iterations = JiveGlobals.getIntProperty("sasl.scram-sha-1.iteration-count",
ScramUtils.DEFAULT_ITERATION_COUNT);
byte[] saltedPassword = null, clientKey = null, storedKey = null, serverKey = null;
try {
saltedPassword = ScramUtils.createSaltedPassword(saltShaker, password, iterations);
clientKey = ScramUtils.computeHmac(saltedPassword, "Client Key");
storedKey = MessageDigest.getInstance("SHA-1").digest(clientKey);
serverKey = ScramUtils.computeHmac(saltedPassword, "Server Key");
} catch (SaslException | NoSuchAlgorithmException | UnsupportedEncodingException e) {
Log.warn("Unable to persist values for SCRAM authentication.");
}
if (!scramOnly && !usePlainPassword) {
try {
encryptedPassword = AuthFactory.encryptPassword(password);
// Set password to null so that it's inserted that way.
......@@ -195,6 +300,10 @@ public class DefaultAuthProvider implements AuthProvider {
// the plain password will be stored.
}
}
if (scramOnly) {
encryptedPassword = null;
password = null;
}
Connection con = null;
PreparedStatement pstmt = null;
......@@ -213,7 +322,21 @@ public class DefaultAuthProvider implements AuthProvider {
else {
pstmt.setString(2, encryptedPassword);
}
pstmt.setString(3, username);
if (storedKey == null) {
pstmt.setNull(3, Types.VARCHAR);
}
else {
pstmt.setString(3, DatatypeConverter.printBase64Binary(storedKey));
}
if (serverKey == null) {
pstmt.setNull(4, Types.VARCHAR);
}
else {
pstmt.setString(4, DatatypeConverter.printBase64Binary(serverKey));
}
pstmt.setString(5, salt);
pstmt.setInt(6, iterations);
pstmt.setString(7, username);
pstmt.executeUpdate();
}
catch (SQLException sqle) {
......@@ -225,6 +348,12 @@ public class DefaultAuthProvider implements AuthProvider {
}
public boolean supportsPasswordRetrieval() {
boolean scramOnly = JiveGlobals.getBooleanProperty("user.scramHashedPasswordOnly");
return !scramOnly;
}
@Override
public boolean isScramSupported() {
return true;
}
}
\ No newline at end of file
......@@ -257,4 +257,10 @@ public class HybridAuthProvider implements AuthProvider {
public boolean supportsPasswordRetrieval() {
return false;
}
@Override
public boolean isScramSupported() {
// TODO Auto-generated method stub
return false;
}
}
\ No newline at end of file
......@@ -409,4 +409,10 @@ public class JDBCAuthProvider implements AuthProvider {
}
}
}
@Override
public boolean isScramSupported() {
// TODO Auto-generated method stub
return false;
}
}
......@@ -204,4 +204,10 @@ public class NativeAuthProvider implements AuthProvider {
public boolean supportsPasswordRetrieval() {
return false;
}
@Override
public boolean isScramSupported() {
// TODO Auto-generated method stub
return false;
}
}
......@@ -245,4 +245,8 @@ public class POP3AuthProvider implements AuthProvider {
public boolean supportsPasswordRetrieval() {
return false;
}
public boolean isScramSupported() {
return false;
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: $
* $Date: $
*
* Copyright 2015 Surevine Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.auth;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.security.sasl.SaslException;
import org.jivesoftware.util.JiveGlobals;
/**
* A utility class that provides methods that are useful for dealing with
* Salted Challenge Response Authentication Mechanism (SCRAM).
*
* @author Richard Midwinter
*/
public class ScramUtils {
public static final int DEFAULT_ITERATION_COUNT = 4096;
private ScramUtils() {}
public static byte[] createSaltedPassword(byte[] salt, String password, int iters) throws SaslException {
Mac mac = createSha1Hmac(password.getBytes(StandardCharsets.US_ASCII));
mac.update(salt);
mac.update(new byte[]{0, 0, 0, 1});
byte[] result = mac.doFinal();
byte[] previous = null;
for (int i = 1; i < iters; i++) {
mac.update(previous != null ? previous : result);
previous = mac.doFinal();
for (int x = 0; x < result.length; x++) {
result[x] ^= previous[x];
}
}
return result;
}
public static byte[] computeHmac(final byte[] key, final String string)
throws SaslException, UnsupportedEncodingException {
Mac mac = createSha1Hmac(key);
mac.update(string.getBytes(StandardCharsets.US_ASCII));
return mac.doFinal();
}
public static Mac createSha1Hmac(final byte[] keyBytes)
throws SaslException {
try {
SecretKeySpec key = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(key);
return mac;
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new SaslException(e.getMessage(), e);
}
}
}
......@@ -131,4 +131,9 @@ public class ClearspaceAuthProvider implements AuthProvider {
public boolean supportsPasswordRetrieval() {
return false;
}
@Override
public boolean isScramSupported() {
return false;
}
}
......@@ -109,4 +109,10 @@ public class CrowdAuthProvider implements AuthProvider {
return false;
}
@Override
public boolean isScramSupported() {
// TODO Auto-generated method stub
return false;
}
}
......@@ -160,4 +160,9 @@ public class LdapAuthProvider implements AuthProvider {
public boolean supportsPasswordRetrieval() {
return false;
}
@Override
public boolean isScramSupported() {
return false;
}
}
\ No newline at end of file
......@@ -32,6 +32,7 @@ import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
......@@ -133,7 +134,39 @@ public class LdapUserProvider implements UserProvider {
}
// Escape the username so that it can be used as a JID.
username = JID.escapeNode(username);
return new User(username, name, email, creationDate, modificationDate);
// As defined by RFC5803.
Attribute authPassword = attrs.get("authPassword");
User user = new User(username, name, email, creationDate, modificationDate);
if (authPassword != null) {
// The authPassword attribute can be multivalued.
// Not sure if this is the right API to loop through them.
NamingEnumeration values = authPassword.getAll();
while (values.hasMore()) {
Attribute authPasswordValue = (Attribute) values.next();
String[] parts = ((String) authPasswordValue.get()).split("$");
String[] authInfo = parts[1].split(":");
String[] authValue = parts[2].split(":");
String scheme = parts[0].trim();
// We only support SCRAM-SHA-1 at the moment.
if ("SCRAM-SHA-1".equals(scheme)) {
int iterations = Integer.valueOf(authInfo[0].trim());
String salt = authInfo[1].trim();
String storedKey = authValue[0].trim();
String serverKey = authValue[1].trim();
user.setSalt(salt);
user.setStoredKey(storedKey);
user.setServerKey(serverKey);
user.setIterations(iterations);
break;
}
}
}
return user;
}
catch (Exception e) {
throw new UserNotFoundException(e);
......
......@@ -802,6 +802,11 @@ public class SASLAuthentication {
it.remove();
}
}
else if (mech.equals("SCRAM-SHA-1")) {
if (!AuthFactory.supportsPasswordRetrieval() && !AuthFactory.supportsScram()) {
it.remove();
}
}
else if (mech.equals("ANONYMOUS")) {
// Check anonymous is supported
if (!XMPPServer.getInstance().getIQAuthHandler().isAnonymousAllowed()) {
......@@ -832,6 +837,7 @@ public class SASLAuthentication {
mechanisms.add("PLAIN");
mechanisms.add("DIGEST-MD5");
mechanisms.add("CRAM-MD5");
mechanisms.add("SCRAM-SHA-1");
mechanisms.add("JIVE-SHAREDSECRET");
}
else {
......@@ -843,6 +849,7 @@ public class SASLAuthentication {
mech.equals("PLAIN") ||
mech.equals("DIGEST-MD5") ||
mech.equals("CRAM-MD5") ||
mech.equals("SCRAM-SHA-1") ||
mech.equals("GSSAPI") ||
mech.equals("EXTERNAL") ||
mech.equals("JIVE-SHAREDSECRET"))
......
......@@ -34,10 +34,12 @@ public class SaslProvider extends Provider {
* Constructs a the JiveSoftware SASL provider.
*/
public SaslProvider() {
super("JiveSoftware", 1.0, "JiveSoftware SASL provider v1.0, implementing server mechanisms for: PLAIN, CLEARSPACE");
super("JiveSoftware", 1.0, "JiveSoftware SASL provider v1.0, implementing server mechanisms for: PLAIN, CLEARSPACE, SCRAM-SHA-1");
// Add SaslServer supporting the PLAIN SASL mechanism
put("SaslServerFactory.PLAIN", "org.jivesoftware.openfire.sasl.SaslServerFactoryImpl");
// Add SaslServer supporting the Clearspace SASL mechanism
put("SaslServerFactory.CLEARSPACE", "org.jivesoftware.openfire.sasl.SaslServerFactoryImpl");
// Add SaslServer supporting the SCRAM-SHA-1 SASL mechanism
put("SaslServerFactory.SCRAM-SHA-1", "org.jivesoftware.openfire.sasl.SaslServerFactoryImpl");
}
}
\ No newline at end of file
......@@ -38,9 +38,10 @@ import org.jivesoftware.openfire.clearspace.ClearspaceSaslServer;
public class SaslServerFactoryImpl implements SaslServerFactory {
private static final String myMechs[] = { "PLAIN", "CLEARSPACE" };
private static final String myMechs[] = { "PLAIN", "CLEARSPACE", "SCRAM-SHA-1" };
private static final int PLAIN = 0;
private static final int CLEARSPACE = 1;
private static final int SCRAM_SHA_1 = 2;
public SaslServerFactoryImpl() {
}
......@@ -70,6 +71,12 @@ public class SaslServerFactoryImpl implements SaslServerFactory {
}
return new ClearspaceSaslServer();
}
else if (mechanism.equals(myMechs[SCRAM_SHA_1])) {
if (cbh == null) {
throw new SaslException("CallbackHandler with support for AuthorizeCallback required");
}
return new ScramSha1SaslServer();
}
return null;
}
......
......@@ -62,14 +62,14 @@ public class DefaultUserProvider implements UserProvider {
private static final Logger Log = LoggerFactory.getLogger(DefaultUserProvider.class);
private static final String LOAD_USER =
"SELECT name, email, creationDate, modificationDate FROM ofUser WHERE username=?";
"SELECT salt, serverKey, storedKey, iterations, name, email, creationDate, modificationDate FROM ofUser WHERE username=?";
private static final String USER_COUNT =
"SELECT count(*) FROM ofUser";
private static final String ALL_USERS =
"SELECT username FROM ofUser ORDER BY username";
private static final String INSERT_USER =
"INSERT INTO ofUser (username,plainPassword,encryptedPassword,name,email,creationDate,modificationDate) " +
"VALUES (?,?,?,?,?,?,?)";
"INSERT INTO ofUser (username,name,email,creationDate,modificationDate) " +
"VALUES (?,?,?,?,?)";
private static final String DELETE_USER_FLAGS =
"DELETE FROM ofUserFlag WHERE username=?";
private static final String DELETE_USER_PROPS =
......@@ -104,12 +104,21 @@ public class DefaultUserProvider implements UserProvider {
if (!rs.next()) {
throw new UserNotFoundException();
}
String name = rs.getString(1);
String email = rs.getString(2);
Date creationDate = new Date(Long.parseLong(rs.getString(3).trim()));
Date modificationDate = new Date(Long.parseLong(rs.getString(4).trim()));
String salt = rs.getString(1);
String serverKey = rs.getString(2);
String storedKey = rs.getString(3);
int iterations = rs.getInt(4);
String name = rs.getString(5);
String email = rs.getString(6);
Date creationDate = new Date(Long.parseLong(rs.getString(7).trim()));
Date modificationDate = new Date(Long.parseLong(rs.getString(8).trim()));
return new User(username, name, email, creationDate, modificationDate);
User user = new User(username, name, email, creationDate, modificationDate);
user.setSalt(salt);
user.setServerKey(serverKey);
user.setStoredKey(storedKey);
user.setIterations(iterations);
return user;
}
catch (Exception e) {
throw new UserNotFoundException(e);
......@@ -129,22 +138,6 @@ public class DefaultUserProvider implements UserProvider {
}
catch (UserNotFoundException unfe) {
// The user doesn't already exist so we can create a new user
// Determine if the password should be stored as plain text or encrypted.
boolean usePlainPassword = JiveGlobals.getBooleanProperty("user.usePlainPassword");
String encryptedPassword = null;
if (!usePlainPassword) {
try {
encryptedPassword = AuthFactory.encryptPassword(password);
// Set password to null so that it's inserted that way.
password = null;
}
catch (UnsupportedOperationException uoe) {
// Encrypting the password may have failed if in setup mode. Therefore,
// use the plain password.
}
}
Date now = new Date();
Connection con = null;
PreparedStatement pstmt = null;
......@@ -152,32 +145,20 @@ public class DefaultUserProvider implements UserProvider {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(INSERT_USER);
pstmt.setString(1, username);
if (password == null) {
pstmt.setNull(2, Types.VARCHAR);
}
else {
pstmt.setString(2, password);
}
if (encryptedPassword == null) {
pstmt.setNull(3, Types.VARCHAR);
}
else {
pstmt.setString(3, encryptedPassword);
}
if (name == null || name.matches("\\s*")) {
pstmt.setNull(4, Types.VARCHAR);
pstmt.setNull(2, Types.VARCHAR);
}
else {
pstmt.setString(4, name);
pstmt.setString(2, name);
}
if (email == null || email.matches("\\s*")) {
pstmt.setNull(5, Types.VARCHAR);
pstmt.setNull(3, Types.VARCHAR);
}
else {
pstmt.setString(5, email);
pstmt.setString(3, email);
}
pstmt.setString(6, StringUtils.dateToMillis(now));
pstmt.setString(7, StringUtils.dateToMillis(now));
pstmt.setString(4, StringUtils.dateToMillis(now));
pstmt.setString(5, StringUtils.dateToMillis(now));
pstmt.execute();
}
catch (Exception e) {
......@@ -186,6 +167,12 @@ public class DefaultUserProvider implements UserProvider {
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
try {
AuthFactory.setPassword(username, password);
} catch(Exception e) {
Log.error("User pasword not set", e);
}
return new User(username, name, email, now, now);
}
}
......
......@@ -84,6 +84,10 @@ public class User implements Cacheable, Externalizable, Result {
private static final String EMAIL_VISIBLE_PROPERTY = "email.visible";
private String username;
private String salt;
private String storedKey;
private String serverKey;
private int iterations;
private String name;
private String email;
private Date creationDate;
......@@ -200,6 +204,38 @@ public class User implements Cacheable, Externalizable, Result {
}
}
public String getStoredKey() {
return storedKey;
}
public void setStoredKey(String storedKey) {
this.storedKey = storedKey;
}
public String getServerKey() {
return serverKey;
}
public void setServerKey(String serverKey) {
this.serverKey = serverKey;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public int getIterations() {
return iterations;
}
public void setIterations(int iterations) {
this.iterations = iterations;
}
public String getName() {
return name == null ? "" : name;
}
......
......@@ -71,6 +71,11 @@
if (password == null) {
errors.put("password", "password");
}
try {
AuthFactory.authenticate("admin", "admin");
} catch (Exception e) {
errors.put("password", "password");
}
if (email == null) {
errors.put("email", "email");
}
......@@ -248,14 +253,15 @@ function checkClick() {
<%
// If the current password is "admin", don't show the text box for them to type
// the current password. This makes setup simpler for first-time users.
String currentPass = null;
boolean defaultPassword = false;
try {
currentPass = AuthFactory.getPassword("admin");
AuthFactory.authenticate("admin", "admin");
defaultPassword = true;
}
catch (Exception e) {
// Ignore.
}
if ("admin".equals(currentPass)) {
if (defaultPassword) {
%>
<input type="hidden" name="password" value="admin">
<%
......
......@@ -25,6 +25,8 @@
JiveGlobals.getProperty("provider.auth.className"));
boolean isCLEARSPACE = "org.jivesoftware.openfire.clearspace.ClearspaceAuthProvider".equals(
JiveGlobals.getProperty("provider.auth.className"));
boolean scramOnly = JiveGlobals.getBooleanProperty("user.scramHashedPasswordOnly");
boolean requestedScramOnly = (request.getParameter("scramOnly") != null);
boolean next = request.getParameter("continue") != null;
if (next) {
// Figure out where to send the user.
......@@ -49,6 +51,9 @@
org.jivesoftware.openfire.security.DefaultSecurityAuditProvider.class.getName()));
xmppSettings.put("provider.admin.className", JiveGlobals.getXMLProperty("provider.admin.className",
org.jivesoftware.openfire.admin.DefaultAdminProvider.class.getName()));
if (requestedScramOnly) {
JiveGlobals.setProperty("user.scramHashedPasswordOnly", "true");
}
// Redirect
response.sendRedirect("setup-admin-settings.jsp");
......@@ -93,6 +98,15 @@
<fmt:message key="setup.profile.default_description" />
</td>
</tr>
<tr>
<td align="center" valign="top">
<input type="checkbox" name="scramOnly" value="scramOnly" id="rb01-0" <% if (scramOnly) { %>checked<% } %>>
</td>
<td>
<label for="rb01-0"><b><fmt:message key="setup.profile.default.scramOnly" /></b></label><br>
<fmt:message key="setup.profile.default.scramOnly_description" />
</td>
</tr>
<tr>
<td align="center" valign="top">
<input type="radio" name="mode" value="ldap" id="rb02" <% if (isLDAP) { %>checked<% } %>>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment