Commit c8afb035 authored by Tom Evans's avatar Tom Evans Committed by tevans

OF-714: Added support for optional property encryption

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@14008 b35dd754-fafc-0310-a699-88a17e54d16e
parent 32a68a5d
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file stores security-related properties needed by Openfire.
You may edit this file to manage encrypted properties and
encryption configuration value. Note however that you should not
edit this file while Openfire is running, or it may be overwritten.
It is important to note that Openfire will store encrypted property
values securely "at rest" (e.g. in the database or XML), but the
values will be managed as clear text strings in memory at runtime for
interoperability and performance reasons. Encrypted property values
are not visible via the Openfire console, but they may be edited or
deleted as needed.
-->
<security>
<encrypt>
<!-- This can be set to "AES" or "Blowfish" (default) at setup time -->
<algorithm>Blowfish</algorithm>
<key>
<!--
If this is a new server setup, you may set a custom encryption key
by setting a value for the <new /> encryption key element only.
To change the encryption key, provide values for both new and old
encryption keys here. The "old" key must match the unencrypted value
of the "current" key. The server will update the existing property
values in the database, re-encrypting them using the new key. After
the encrypted properties have been updated, the new key will itself
be encrypted and re-written into this file as <current />.
Note that if the current encryption key becomes invalid, any property
values secured by the original key will be inaccessible as well.
The key value can be any string, and it will be hashed, filled, and/or
truncated to produce a compatible key for the corresponding algorithm.
Note that leading and trailing spaces will be ignored. A strong key
will contain sixteen characters or more.
<old></old>
<new></new>
-->
<current />
</key>
<property>
<!--
This list includes the names of properties that have been marked for
encryption. Any XML properties (from openfire.xml) that are listed here
will be encrypted automatically upon first use. Other properties
(already in the database) can be added to this list at runtime via the
"System Properties" page in the Openfire console.
-->
<name>database.defaultProvider.username</name>
<name>database.defaultProvider.password</name>
<name>ldap.adminDN</name>
<name>ldap.adminPassword</name>
<name>clearspace.uri</name>
<name>clearspace.sharedSecret</name>
</property>
</encrypt>
<!--
Any other property defined in this file will be treated as an encrypted
property. The value (in clear text) will be encrypted and migrated into
the Openfire database during the next startup. The property name will
be added to the list of encrypted properties and the clear text value
will be removed from this file.
<foo><bar>Secr3t$tr1ng!</bar></foo>
-->
</security>
......@@ -518,6 +518,23 @@
## Added key: 'session.details.node'
## Added key: 'session.details.local'
## Added key: 'session.details.remote'
##
## 3.9.2
## Added key: 'server.properties.encryption'
## Added key: 'server.properties.encrypted'
## Added key: 'server.properties.encrypt'
## Added key: 'server.properties.alt_encrypt'
## Added key: 'server.properties.encrypt_property_true'
## Added key: 'server.properties.encrypt_property_false'
## Added key: 'setup.host.settings.encryption_algorithm'
## Added key: 'setup.host.settings.encryption_algorithm_info'
## Added key: 'setup.host.settings.encryption_aes'
## Added key: 'setup.host.settings.encryption_blowfish'
## Added key: 'setup.host.settings.encryption_key'
## Added key: 'setup.host.settings.encryption_key_info'
## Added key: 'setup.host.settings.encryption_key_invalid'
## Added key: 'server.properties.delete_confirm'
## Added key: 'server.properties.encrypt_confirm'
# Openfire
......@@ -1499,7 +1516,7 @@ server.db_stats.no_queries=No queries
# Server properties Page
server.properties.title=System Properties
server.properties.info=Below is a list of the system properties. Values for password sensitive fields are \
server.properties.info=Below is a list of the system properties. Values for encrypted and sensitive fields are \
hidden. Long property names and values are clipped. Hold your mouse over the property name to see \
the full value or to see both the full name and value, click the edit icon next to the property.
server.properties.system=System Properties
......@@ -1519,6 +1536,15 @@ server.properties.new_property=Add new property
server.properties.enter_property_name=Please enter a valid property name.
server.properties.enter_property_value=Please enter a property value.
server.properties.max_character=1000 character max.
server.properties.encryption=Property Encryption
server.properties.encrypted=Property encrypted successfully.
server.properties.encrypt=Encrypt
server.properties.alt_encrypt=Click to encrypt this property
server.properties.alt_encrypted=Property is encrypted
server.properties.encrypt_property_true=Encrypt this property value
server.properties.encrypt_property_false=Do not encrypt this property value
server.properties.delete_confirm=Are you sure you want to delete this property?
server.properties.encrypt_confirm=Are you sure you want to encrypt this property?
# Server props Page
......@@ -2145,6 +2171,13 @@ setup.host.settings.secure_port=Secure Admin Console Port:
setup.host.settings.invalid_port=Invalid port number
setup.host.settings.port_number=Port number for the web-based admin console (default is 9090).
setup.host.settings.secure_port_number=Port number for the web-based admin console through SSL (default is 9091).
setup.host.settings.encryption_algorithm=Property Encryption via:
setup.host.settings.encryption_algorithm_info=Select an encryption engine/algorithm to use when encrypting properties.
setup.host.settings.encryption_aes=AES
setup.host.settings.encryption_blowfish=Blowfish
setup.host.settings.encryption_key=Property Encryption Key:
setup.host.settings.encryption_key_info=For additional security, you may optionally define a custom encryption key.
setup.host.settings.encryption_key_invalid=You must input the same encryption key value twice to ensure that you have typed the hidden value correctly.
# Setup index Page
......
package org.jivesoftware.util;
import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class providing symmetric AES encryption/decryption. To strengthen
* the encrypted result, use the {@link #setKey} method to provide a custom
* key prior to invoking the {@link #encrypt} or {@link #decrypt} methods.
*
* @author Tom Evans
*/
public class AesEncryptor implements Encryptor {
private static final Logger log = LoggerFactory.getLogger(AesEncryptor.class);
private static final String ALGORITHM = "AES/CBC/PKCS7Padding";
private static final byte[] INIT_PARM =
{
(byte)0xcd, (byte)0x91, (byte)0xa7, (byte)0xc5,
(byte)0x27, (byte)0x8b, (byte)0x39, (byte)0xe0,
(byte)0xfa, (byte)0x72, (byte)0xd0, (byte)0x29,
(byte)0x83, (byte)0x65, (byte)0x9d, (byte)0x74
};
private static final byte[] DEFAULT_KEY =
{
(byte)0xf2, (byte)0x46, (byte)0x5d, (byte)0x2a,
(byte)0xd1, (byte)0x73, (byte)0x0b, (byte)0x18,
(byte)0xcb, (byte)0x86, (byte)0x95, (byte)0xa3,
(byte)0xb1, (byte)0xe5, (byte)0x89, (byte)0x27
};
private static boolean isInitialized = false;
private byte[] cipherKey = null;
/** Default constructor */
public AesEncryptor() { initialize(); }
/** Custom key constructor */
public AesEncryptor(String key) {
initialize();
setKey(key);
}
/* (non-Javadoc)
* @see org.jivesoftware.util.Encryptor#encrypt(java.lang.String)
*/
@Override
public String encrypt(String value)
{
if (value == null) { return null; }
byte [] bytes = null;
try { bytes = value.getBytes("UTF-8"); }
catch (UnsupportedEncodingException uee) { bytes = value.getBytes(); }
return Base64.encodeBytes( cipher(bytes, getKey(), Cipher.ENCRYPT_MODE) );
}
/* (non-Javadoc)
* @see org.jivesoftware.util.Encryptor#decrypt(java.lang.String)
*/
@Override
public String decrypt(String value)
{
if (value == null) { return null; }
byte [] bytes = cipher(Base64.decode(value), getKey(), Cipher.DECRYPT_MODE);
if (bytes == null) { return null; }
String result = null;
try { result = new String(bytes,"UTF-8"); }
catch (UnsupportedEncodingException uee) { result = new String(bytes); }
return result;
}
/**
* Symmetric encrypt/decrypt routine.
*
* @param attribute The value to be converted
* @param key The encryption key
* @param mode The cipher mode (encrypt or decrypt)
* @return The converted attribute, or null if conversion fails
*/
private byte [] cipher(byte [] attribute, byte [] key, int mode)
{
byte [] result = null;
try
{
// Create AES encryption key
Key aesKey = new SecretKeySpec(key, "AES");
// Create AES Cipher
Cipher aesCipher = Cipher.getInstance(ALGORITHM);
// Initialize AES Cipher and convert
aesCipher.init(mode, aesKey, new IvParameterSpec(INIT_PARM));
result = aesCipher.doFinal(attribute);
}
catch (Exception e)
{
log.error("AES cipher failed", e);
}
return result;
}
/**
* Return the encryption key. This will return the user-defined
* key (if available) or a default encryption key.
*
* @return The encryption key
*/
private byte [] getKey()
{
return cipherKey == null ? DEFAULT_KEY : cipherKey;
}
/**
* Set the encryption key. This will apply the user-defined key,
* truncated or filled (via the default key) as needed to meet
* the key length specifications.
*
* @param key The encryption key
*/
private void setKey(byte [] key)
{
cipherKey = editKey(key);
}
/* (non-Javadoc)
* @see org.jivesoftware.util.Encryptor#setKey(java.lang.String)
*/
@Override
public void setKey(String key)
{
if (key == null) {
cipherKey = null;
return;
}
byte [] bytes = null;
try { bytes = key.getBytes("UTF-8"); }
catch (UnsupportedEncodingException uee) { bytes = key.getBytes(); }
setKey(editKey(bytes));
}
/**
* Validates an optional user-defined encryption key. Only the
* first sixteen bytes of the input array will be used for the key.
* It will be filled (if necessary) to a minimum length of sixteen.
*
* @param key The user-defined encryption key
* @return A valid encryption key, or null
*/
private byte [] editKey(byte [] key)
{
if (key == null) { return null; }
byte [] result = new byte [DEFAULT_KEY.length];
for (int x=0; x<DEFAULT_KEY.length; x++)
{
result[x] = x < key.length ? key[x] : DEFAULT_KEY[x];
}
return result;
}
/** Installs the required security provider(s) */
private synchronized void initialize()
{
if (!isInitialized)
{
try
{
Security.addProvider(new BouncyCastleProvider());
isInitialized = true;
}
catch (Throwable t)
{
log.warn("JCE provider failure; unable to load BC", t);
}
}
}
/* */
}
......@@ -26,12 +26,20 @@ import org.slf4j.LoggerFactory;
* @author Markus Hahn <markus_hahn@gmx.net>
* @author Gaston Dombiak
*/
public class Blowfish {
public class Blowfish implements Encryptor {
private static final Logger Log = LoggerFactory.getLogger(Blowfish.class);
private BlowfishCBC m_bfish;
private static Random m_rndGen = new Random();
private static final String DEFAULT_KEY = "Blowfish-CBC";
/**
* Creates a new Blowfish object using the default key
*/
public Blowfish() {
setKey(DEFAULT_KEY);
}
/**
* Creates a new Blowfish object using the specified key (oversized
......@@ -40,19 +48,7 @@ public class Blowfish {
* @param password the password (treated as a real unicode array)
*/
public Blowfish(String password) {
// hash down the password to a 160bit key
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA1");
digest.update(password.getBytes());
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
// setup the encryptor (use a dummy IV)
m_bfish = new BlowfishCBC(digest.digest(), 0);
digest.reset();
setKey(password);
}
/**
......@@ -64,6 +60,7 @@ public class Blowfish {
* @return encrypted string in binhex format
*/
public String encryptString(String sPlainText) {
if (sPlainText == null) { return null; }
// get the IV
long lCBCIV;
synchronized (m_rndGen)
......@@ -1484,5 +1481,36 @@ public class Blowfish {
return sbuf.toString();
}
// Encryptor interface
@Override
public String encrypt(String value) {
return this.encryptString(value);
}
@Override
public String decrypt(String value) {
return this.decryptString(value);
}
@Override
public void setKey(String key) {
String password = key == null ? DEFAULT_KEY : key;
// hash down the password to a 160bit key
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA1");
digest.update(password.getBytes());
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
// setup the encryptor (use a dummy IV)
m_bfish = new BlowfishCBC(digest.digest(), 0);
digest.reset();
}
}
package org.jivesoftware.util;
public interface Encryptor {
/**
* Encrypt a clear text String.
*
* @param value The clear text attribute
* @return The encrypted attribute, or null
*/
public abstract String encrypt(String value);
/**
* Decrypt an encrypted String.
*
* @param value The encrypted attribute in Base64 encoding
* @return The clear text attribute, or null
*/
public abstract String decrypt(String value);
/**
* Set the encryption key. This will apply the user-defined key,
* truncated or filled (via the default key) as needed to meet
* the key length specifications.
*
* @param key The encryption key
*/
public abstract void setKey(String key);
}
\ No newline at end of file
......@@ -30,10 +30,12 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.TimerTask;
import org.jivesoftware.database.DbConnectionManager;
import org.slf4j.Logger;
......@@ -51,13 +53,23 @@ import org.slf4j.LoggerFactory;
* setting the home directory and path to the configuration file.<p>
*
* XML property names must be in the form <code>prop.name</code> - parts of the name must
* be seperated by ".". The value can be any valid String, including strings with line breaks.
* be separated by ".". The value can be any valid String, including strings with line breaks.
*/
public class JiveGlobals {
private static final Logger Log = LoggerFactory.getLogger(JiveGlobals.class);
private static String JIVE_CONFIG_FILENAME = "conf" + File.separator + "openfire.xml";
private static final String JIVE_SECURITY_FILENAME = "conf" + File.separator + "security.xml";
private static final String ENCRYPTED_PROPERTY_NAME_PREFIX = "encrypt.";
private static final String ENCRYPTED_PROPERTY_NAMES = ENCRYPTED_PROPERTY_NAME_PREFIX + "property.name";
private static final String ENCRYPTION_ALGORITHM = ENCRYPTED_PROPERTY_NAME_PREFIX + "algorithm";
private static final String ENCRYPTION_KEY_CURRENT = ENCRYPTED_PROPERTY_NAME_PREFIX + "key.current";
private static final String ENCRYPTION_KEY_NEW = ENCRYPTED_PROPERTY_NAME_PREFIX + "key.new";
private static final String ENCRYPTION_KEY_OLD = ENCRYPTED_PROPERTY_NAME_PREFIX + "key.old";
private static final String ENCRYPTION_ALGORITHM_AES = "AES";
private static final String ENCRYPTION_ALGORITHM_BLOWFISH = "Blowfish";
/**
* Location of the jiveHome directory. All configuration files should be
......@@ -67,7 +79,8 @@ public class JiveGlobals {
public static boolean failedLoading = false;
private static XMLProperties xmlProperties = null;
private static XMLProperties openfireProperties = null;
private static XMLProperties securityProperties = null;
private static JiveProperties properties = null;
private static Locale locale = null;
......@@ -75,19 +88,22 @@ public class JiveGlobals {
private static DateFormat dateFormat = null;
private static DateFormat dateTimeFormat = null;
private static DateFormat timeFormat = null;
private static Encryptor propertyEncryptor = null;
private static String currentKey = null;
/**
* Returns the global Locale used by Jive. A locale specifies language
* and country codes, and is used for internationalization. The default
* locale is system dependant - Locale.getDefault().
* locale is system dependent - Locale.getDefault().
*
* @return the global locale used by Jive.
*/
public static Locale getLocale() {
if (locale == null) {
if (xmlProperties != null) {
if (openfireProperties != null) {
String [] localeArray;
String localeProperty = xmlProperties.getProperty("locale");
String localeProperty = openfireProperties.getProperty("locale");
if (localeProperty != null) {
localeArray = localeProperty.split("_");
}
......@@ -251,8 +267,8 @@ public class JiveGlobals {
* @return the location of the home dir.
*/
public static String getHomeDirectory() {
if (xmlProperties == null) {
loadSetupProperties();
if (openfireProperties == null) {
loadOpenfireProperties();
}
return home;
}
......@@ -297,16 +313,16 @@ public class JiveGlobals {
* @return the property value specified by name.
*/
public static String getXMLProperty(String name) {
if (xmlProperties == null) {
loadSetupProperties();
if (openfireProperties == null) {
loadOpenfireProperties();
}
// home not loaded?
if (xmlProperties == null) {
if (openfireProperties == null) {
return null;
}
return xmlProperties.getProperty(name);
return openfireProperties.getProperty(name);
}
/**
......@@ -329,16 +345,16 @@ public class JiveGlobals {
* @return the property value specified by name.
*/
public static String getXMLProperty(String name, String defaultValue) {
if (xmlProperties == null) {
loadSetupProperties();
if (openfireProperties == null) {
loadOpenfireProperties();
}
// home not loaded?
if (xmlProperties == null) {
return null;
if (openfireProperties == null) {
return defaultValue;
}
String value = xmlProperties.getProperty(name);
String value = openfireProperties.getProperty(name);
if (value == null) {
value = defaultValue;
}
......@@ -426,13 +442,13 @@ public class JiveGlobals {
* @param value the value of the property being set.
*/
public static void setXMLProperty(String name, String value) {
if (xmlProperties == null) {
loadSetupProperties();
if (openfireProperties == null) {
loadOpenfireProperties();
}
// jiveHome not loaded?
if (xmlProperties != null) {
xmlProperties.setProperty(name, value);
if (openfireProperties != null) {
openfireProperties.setProperty(name, value);
}
}
......@@ -453,12 +469,12 @@ public class JiveGlobals {
* @param propertyMap a map of properties, keyed on property name.
*/
public static void setXMLProperties(Map<String, String> propertyMap) {
if (xmlProperties == null) {
loadSetupProperties();
if (openfireProperties == null) {
loadOpenfireProperties();
}
if (xmlProperties != null) {
xmlProperties.setProperties(propertyMap);
if (openfireProperties != null) {
openfireProperties.setProperties(propertyMap);
}
}
......@@ -485,16 +501,16 @@ public class JiveGlobals {
* @return all child property values for the given parent.
*/
public static List getXMLProperties(String parent) {
if (xmlProperties == null) {
loadSetupProperties();
if (openfireProperties == null) {
loadOpenfireProperties();
}
// jiveHome not loaded?
if (xmlProperties == null) {
if (openfireProperties == null) {
return Collections.EMPTY_LIST;
}
String[] propNames = xmlProperties.getChildrenProperties(parent);
String[] propNames = openfireProperties.getChildrenProperties(parent);
List<String> values = new ArrayList<String>();
for (String propName : propNames) {
String value = getXMLProperty(parent + "." + propName);
......@@ -513,10 +529,10 @@ public class JiveGlobals {
* @param name the name of the property to delete.
*/
public static void deleteXMLProperty(String name) {
if (xmlProperties == null) {
loadSetupProperties();
if (openfireProperties == null) {
loadOpenfireProperties();
}
xmlProperties.deleteProperty(name);
openfireProperties.deleteProperty(name);
}
/**
......@@ -762,22 +778,105 @@ public class JiveGlobals {
if (isSetupMode()) {
return;
}
if (getXMLProperty(name) != null) {
if (getProperty(name) == null) {
Log.debug("JiveGlobals: Migrating XML property '"+name+"' into database.");
setProperty(name, getXMLProperty(name));
deleteXMLProperty(name);
}
else if (getProperty(name).equals(getXMLProperty(name))) {
Log.debug("JiveGlobals: Deleting duplicate XML property '"+name+"' that is already in database.");
deleteXMLProperty(name);
}
else if (!getProperty(name).equals(getXMLProperty(name))) {
Log.warn("Property '"+name+"' as specified in openfire.xml differs from what is stored in the database. Please make property changes in the database instead of openfire.xml.");
}
}
openfireProperties.migrateProperty(name);
}
/**
* Flags certain properties as being sensitive, based on
* property naming conventions. Values for matching property
* names are hidden from the Openfire console.
*
* @param name The name of the property
* @returns True if the property is considered sensitive, otherwise false
*/
public static boolean isPropertySensitive(String name) {
return name != null && (
name.toLowerCase().indexOf("passwd") > -1 ||
name.toLowerCase().indexOf("password") > -1 ||
name.toLowerCase().indexOf("cookiekey") > -1);
}
/**
* Determines whether a property is configured for encryption.
*
* @param name The name of the property
* @returns True if the property is stored using encryption, otherwise false
*/
public static boolean isPropertyEncrypted(String name) {
if (securityProperties == null) {
loadSecurityProperties();
}
return name != null &&
!name.startsWith(ENCRYPTED_PROPERTY_NAME_PREFIX) &&
securityProperties.getProperties(ENCRYPTED_PROPERTY_NAMES, true).contains(name);
}
/**
* Set the encryption status for the given property.
*
* @param name The name of the property
* @param encrypt True to encrypt the property, false to decrypt
* @returns True if the property's encryption status changed, otherwise false
*/
public static boolean setPropertyEncrypted(String name, boolean encrypt) {
if (securityProperties == null) {
loadSecurityProperties();
}
boolean propertyWasChanged;
if (isPropertyEncrypted(name)) {
propertyWasChanged = securityProperties.removeFromList(ENCRYPTED_PROPERTY_NAMES, name);
} else {
propertyWasChanged = securityProperties.addToList(ENCRYPTED_PROPERTY_NAMES, name);
}
if (propertyWasChanged) {
resetProperty(name);
}
return propertyWasChanged;
}
/**
* Fetches the current value of the property encryption key.
*
* @returns The property encryption key
*/
public static Encryptor getPropertyEncryptor() {
if (securityProperties == null) {
loadSecurityProperties();
}
if (propertyEncryptor == null) {
String algorithm = securityProperties.getProperty(ENCRYPTION_ALGORITHM);
if (ENCRYPTION_ALGORITHM_AES.equalsIgnoreCase(algorithm)) {
propertyEncryptor = new AesEncryptor(currentKey);
} else {
propertyEncryptor = new Blowfish(currentKey);
}
}
return propertyEncryptor;
}
/**
* This method is called early during the setup process to
* set the algorithm for encrypting property values
*/
public static void setupPropertyEncryptionAlgorithm(String alg) {
if (ENCRYPTION_ALGORITHM_AES.equalsIgnoreCase(alg)) {
securityProperties.setProperty(ENCRYPTION_ALGORITHM, ENCRYPTION_ALGORITHM_AES);
} else {
securityProperties.setProperty(ENCRYPTION_ALGORITHM, ENCRYPTION_ALGORITHM_BLOWFISH);
}
}
/**
* This method is called early during the setup process to
* set a custom key for encrypting property values
*/
public static void setupPropertyEncryptionKey(String key) {
currentKey = key;
securityProperties.setProperty(ENCRYPTION_KEY_CURRENT, new AesEncryptor().encrypt(currentKey));
}
/**
* Allows the name of the local config file name to be changed. The
* default is "openfire.xml".
......@@ -789,7 +888,7 @@ public class JiveGlobals {
}
/**
* Returns the name of the local config file name.
* Returns the name of the local config file.
*
* @return the name of the config file.
*/
......@@ -799,9 +898,9 @@ public class JiveGlobals {
/**
* Returns true if in setup mode. A false value means that setup has been completed
* or that a connection to the database was possible to properies stored in the
* datbase can be retrieved now. The latter means that once the database settings
* during the setup was done a connection to the datbase should be available thus
* or that a connection to the database was possible to properties stored in the
* database can be retrieved now. The latter means that once the database settings
* during the setup was done a connection to the database should be available thus
* properties stored from a previous setup will be available.
*
* @return true if in setup mode.
......@@ -832,11 +931,11 @@ public class JiveGlobals {
}
/**
* Loads properties if necessary. Property loading must be done lazily so
* Loads Openfire properties if necessary. Property loading must be done lazily so
* that we give outside classes a chance to set <tt>home</tt>.
*/
private synchronized static void loadSetupProperties() {
if (xmlProperties == null) {
private synchronized static void loadOpenfireProperties() {
if (openfireProperties == null) {
// If home is null then log that the application will not work correctly
if (home == null && !failedLoading) {
failedLoading = true;
......@@ -845,10 +944,10 @@ public class JiveGlobals {
msg.append("which will prevent the application from working correctly.\n\n");
System.err.println(msg.toString());
}
// Create a manager with the full path to the xml config file.
// Create a manager with the full path to the Openfire config file.
else {
try {
xmlProperties = new XMLProperties(home + File.separator + getConfigName());
openfireProperties = new XMLProperties(home + File.separator + getConfigName());
}
catch (IOException ioe) {
Log.error(ioe.getMessage(), ioe);
......@@ -857,4 +956,132 @@ public class JiveGlobals {
}
}
}
/**
* Lazy-loads the security configuration properties.
*/
private synchronized static void loadSecurityProperties() {
if (securityProperties == null) {
// If home is null then log that the application will not work correctly
if (home == null && !failedLoading) {
failedLoading = true;
StringBuilder msg = new StringBuilder();
msg.append("Critical Error! The home directory has not been configured, \n");
msg.append("which will prevent the application from working correctly.\n\n");
System.err.println(msg.toString());
try {
securityProperties = new XMLProperties();
} catch (IOException ioe) {
Log.error("Failed to setup default secuirty properties", ioe);
}
}
// Create a manager with the full path to the security XML file.
else {
try {
securityProperties = new XMLProperties(home + File.separator + JIVE_SECURITY_FILENAME);
setupPropertyEncryption();
TaskEngine.getInstance().schedule(new TimerTask() {
public void run() {
// Migrate all secure XML properties into the database automatically
for (String propertyName : securityProperties.getAllPropertyNames()) {
if (!propertyName.startsWith(ENCRYPTED_PROPERTY_NAME_PREFIX)) {
setPropertyEncrypted(propertyName, true);
securityProperties.migrateProperty(propertyName);
}
}
}
}, 1000);
}
catch (IOException ioe) {
Log.error(ioe.getMessage(), ioe);
failedLoading = true;
}
}
}
}
/**
* Setup the property encryption key, rewriting encrypted values as appropriate
*/
private static void setupPropertyEncryption() {
// get/set the current encryption key
Encryptor keyEncryptor = new AesEncryptor();
String encryptedKey = securityProperties.getProperty(ENCRYPTION_KEY_CURRENT);
if (encryptedKey == null || encryptedKey.isEmpty()) {
currentKey = null;
} else {
currentKey = keyEncryptor.decrypt(encryptedKey);
}
// check to see if a new key has been defined
String newKey = securityProperties.getProperty(ENCRYPTION_KEY_NEW, false);
if (newKey != null) {
Log.info("Detected new encryption key; updating encrypted properties");
// if a new key has been provided, check to see if the old key matches
// the current key, otherwise log an error and ignore the new key
String oldKey = securityProperties.getProperty(ENCRYPTION_KEY_OLD);
if (oldKey == null) {
if (currentKey != null) {
Log.warn("Old encryption key was not provided; ignoring new encryption key");
return;
}
} else {
if (!oldKey.equals(currentKey)) {
Log.warn("Old encryption key does not match current encryption key; ignoring new encryption key");
return;
}
}
// load DB properties using the current key
if (properties == null) {
properties = JiveProperties.getInstance();
}
// load XML properties using the current key
Map<String, String> openfireProps = new HashMap<String, String>();
for (String xmlProp : openfireProperties.getAllPropertyNames()) {
if (isPropertyEncrypted(xmlProp)) {
openfireProps.put(xmlProp, openfireProperties.getProperty(xmlProp));
}
}
// rewrite existing encrypted properties using new encryption key
currentKey = newKey == null || newKey.isEmpty() ? null : newKey;
propertyEncryptor = null;
for (String propertyName : securityProperties.getProperties(ENCRYPTED_PROPERTY_NAMES, true)) {
Log.info("Updating encrypted value for " + propertyName);
if (openfireProps.containsKey(propertyName)) {
openfireProperties.setProperty(propertyName, openfireProps.get(propertyName));
} else if (!resetProperty(propertyName)) {
Log.warn("Failed to reset encrypted property value for " + propertyName);
};
}
securityProperties.deleteProperty(ENCRYPTION_KEY_NEW);
securityProperties.deleteProperty(ENCRYPTION_KEY_OLD);
}
// (re)write the encryption key to the security XML file
securityProperties.setProperty(ENCRYPTION_KEY_CURRENT, keyEncryptor.encrypt(currentKey));
}
/**
* Read and re-write a given property to reset its encryption status
* @param propertyName
*/
private static boolean resetProperty(String propertyName) {
if (properties != null) {
String propertyValue = properties.get(propertyName);
if (propertyValue != null) {
properties.remove(propertyName);
properties.put(propertyName, propertyValue);
return true;
}
}
return false;
}
}
\ No newline at end of file
......@@ -51,12 +51,7 @@ public class JiveProperties implements Map<String, String> {
private static final String UPDATE_PROPERTY = "UPDATE ofProperty SET propValue=? WHERE name=?";
private static final String DELETE_PROPERTY = "DELETE FROM ofProperty WHERE name LIKE ?";
private static class JivePropertyHolder {
private static final JiveProperties instance = new JiveProperties();
static {
instance.init();
}
}
private static JiveProperties instance = null;
private Map<String, String> properties;
......@@ -65,16 +60,19 @@ public class JiveProperties implements Map<String, String> {
*
* @return an instance of JiveProperties.
*/
public static JiveProperties getInstance() {
return JivePropertyHolder.instance;
}
private JiveProperties() {
public synchronized static JiveProperties getInstance() {
if (instance == null) {
JiveProperties props = new JiveProperties();
props.init();
instance = props;
}
return instance;
}
private JiveProperties() { }
/**
* For internal use only. This method allows for the reloading of all properties from the
* values in the datatabase. This is required since it's quite possible during the setup
* values in the database. This is required since it's quite possible during the setup
* process that a database connection will not be available till after this class is
* initialized. Thus, if there are existing properties in the database we will want to reload
* this class after the setup process has been completed.
......@@ -192,7 +190,7 @@ public class JiveProperties implements Map<String, String> {
PropertyEventDispatcher.dispatchEvent((String)key, PropertyEventDispatcher.EventType.property_deleted, params);
// Send update to other cluster members.
CacheFactory.doClusterTask(PropertyClusterEventTask.createDeteleTask((String) key));
CacheFactory.doClusterTask(PropertyClusterEventTask.createDeleteTask((String) key));
return value;
}
......@@ -284,13 +282,14 @@ public class JiveProperties implements Map<String, String> {
}
private void insertProperty(String name, String value) {
Encryptor encryptor = getEncryptor();
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(INSERT_PROPERTY);
pstmt.setString(1, name);
pstmt.setString(2, value);
pstmt.setString(2, JiveGlobals.isPropertyEncrypted(name) ? encryptor.encrypt(value) : value);
pstmt.executeUpdate();
}
catch (SQLException e) {
......@@ -302,12 +301,13 @@ public class JiveProperties implements Map<String, String> {
}
private void updateProperty(String name, String value) {
Encryptor encryptor = getEncryptor();
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(UPDATE_PROPERTY);
pstmt.setString(1, value);
pstmt.setString(1, JiveGlobals.isPropertyEncrypted(name) ? encryptor.encrypt(value) : value);
pstmt.setString(2, name);
pstmt.executeUpdate();
}
......@@ -337,6 +337,7 @@ public class JiveProperties implements Map<String, String> {
}
private void loadProperties() {
Encryptor encryptor = getEncryptor();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
......@@ -347,7 +348,17 @@ public class JiveProperties implements Map<String, String> {
while (rs.next()) {
String name = rs.getString(1);
String value = rs.getString(2);
properties.put(name, value);
if (JiveGlobals.isPropertyEncrypted(name)) {
try {
value = encryptor.decrypt(value);
} catch (Exception ex) {
Log.error("Failed to load encrypted property value for " + name, ex);
value = null;
}
}
if (value != null) {
properties.put(name, value);
}
}
}
catch (Exception e) {
......@@ -357,4 +368,8 @@ public class JiveProperties implements Map<String, String> {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
private Encryptor getEncryptor() {
return JiveGlobals.getPropertyEncryptor();
}
}
\ No newline at end of file
......@@ -46,7 +46,7 @@ public class PropertyClusterEventTask implements ClusterTask {
return task;
}
public static PropertyClusterEventTask createDeteleTask(String key) {
public static PropertyClusterEventTask createDeleteTask(String key) {
PropertyClusterEventTask task = new PropertyClusterEventTask();
task.event = Type.deleted;
task.key = key;
......
......@@ -33,8 +33,10 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
......@@ -43,6 +45,7 @@ import java.util.Map;
import java.util.StringTokenizer;
import org.apache.commons.lang.StringEscapeUtils;
import org.dom4j.Attribute;
import org.dom4j.CDATA;
import org.dom4j.Document;
import org.dom4j.Element;
......@@ -64,7 +67,7 @@ import org.slf4j.LoggerFactory;
* </pre>
* <p/>
* The XML file is passed in to the constructor and must be readable and
* writtable. Setting property values will automatically persist those value
* writable. Setting property values will automatically persist those value
* to disk. The file encoding used is UTF-8.
*
* @author Derek DeMoro
......@@ -73,6 +76,7 @@ import org.slf4j.LoggerFactory;
public class XMLProperties {
private static final Logger Log = LoggerFactory.getLogger(XMLProperties.class);
private static final String ENCRYPTED_ATTRIBUTE = "encrypted";
private File file;
private Document document;
......@@ -83,6 +87,15 @@ public class XMLProperties {
*/
private Map<String, String> propertyCache = new HashMap<String, String>();
/**
* Creates a new empty XMLPropertiesTest object.
*
* @throws IOException if an error occurs loading the properties.
*/
public XMLProperties() throws IOException {
buildDoc(new StringReader("<root />"));
}
/**
* Creates a new XMLPropertiesTest object.
*
......@@ -152,13 +165,24 @@ public class XMLProperties {
* @return the value of the specified property.
*/
public synchronized String getProperty(String name) {
return getProperty(name, true);
}
/**
* Returns the value of the specified property.
*
* @param name the name of the property to get.
* @param ignoreEmpty Ignore empty property values (return null)
* @return the value of the specified property.
*/
public synchronized String getProperty(String name, boolean ignoreEmpty) {
String value = propertyCache.get(name);
if (value != null) {
return value;
}
String[] propName = parsePropertyName(name);
// Search for this property by traversing down the XML heirarchy.
// Search for this property by traversing down the XML hierarchy.
Element element = document.getRootElement();
for (String aPropName : propName) {
element = element.element(aPropName);
......@@ -171,10 +195,21 @@ public class XMLProperties {
// At this point, we found a matching property, so return its value.
// Empty strings are returned as null.
value = element.getTextTrim();
if ("".equals(value)) {
if (ignoreEmpty && "".equals(value)) {
return null;
}
else {
// check to see if the property is marked as encrypted
if (JiveGlobals.isPropertyEncrypted(name)) {
Attribute encrypted = element.attribute(ENCRYPTED_ATTRIBUTE);
if (encrypted != null) {
value = JiveGlobals.getPropertyEncryptor().decrypt(value);
} else {
// rewrite property as an encrypted value
Log.info("Rewriting XML property " + name + " as an encrypted value");
setProperty(name, value);
}
}
// Add to cache so that getting property next time is fast.
propertyCache.put(name, value);
return value;
......@@ -202,9 +237,10 @@ public class XMLProperties {
* @param name the name of the property to retrieve
* @return all child property values for the given node name.
*/
public String[] getProperties(String name) {
public List<String> getProperties(String name, boolean asList) {
List<String> result = new ArrayList<String>();
String[] propName = parsePropertyName(name);
// Search for this property by traversing down the XML heirarchy,
// Search for this property by traversing down the XML hierarchy,
// stopping one short.
Element element = document.getRootElement();
for (int i = 0; i < propName.length - 1; i++) {
......@@ -212,22 +248,64 @@ public class XMLProperties {
if (element == null) {
// This node doesn't match this part of the property name which
// indicates this property doesn't exist so return empty array.
return new String[]{};
return result;
}
}
// We found matching property, return names of children.
Iterator iter = element.elementIterator(propName[propName.length - 1]);
List<String> props = new ArrayList<String>();
Iterator<Element> iter = element.elementIterator(propName[propName.length - 1]);
Element prop;
String value;
boolean updateEncryption = false;
while (iter.hasNext()) {
prop = iter.next();
// Empty strings are skipped.
value = ((Element)iter.next()).getTextTrim();
value = prop.getTextTrim();
if (!"".equals(value)) {
props.add(value);
// check to see if the property is marked as encrypted
if (JiveGlobals.isPropertyEncrypted(name)) {
Attribute encrypted = prop.attribute(ENCRYPTED_ATTRIBUTE);
if (encrypted != null) {
value = JiveGlobals.getPropertyEncryptor().decrypt(value);
} else {
// rewrite property as an encrypted value
prop.addAttribute(ENCRYPTED_ATTRIBUTE, "true");
updateEncryption = true;
}
}
result.add(value);
}
}
String[] childrenNames = new String[props.size()];
return props.toArray(childrenNames);
if (updateEncryption) {
Log.info("Rewriting values for XML property " + name + " using encryption");
saveProperties();
}
return result;
}
/**
* Return all values who's path matches the given property
* name as a String array, or an empty array if the if there
* are no children. This allows you to retrieve several values
* with the same property name. For example, consider the
* XML file entry:
* <pre>
* &lt;foo&gt;
* &lt;bar&gt;
* &lt;prop&gt;some value&lt;/prop&gt;
* &lt;prop&gt;other value&lt;/prop&gt;
* &lt;prop&gt;last value&lt;/prop&gt;
* &lt;/bar&gt;
* &lt;/foo&gt;
* </pre>
* If you call getProperties("foo.bar.prop") will return a string array containing
* {"some value", "other value", "last value"}.
*
* @deprecated Retained for backward compatibility. Prefer getProperties(String, boolean)
* @param name the name of the property to retrieve
* @return all child property values for the given node name.
*/
public String[] getProperties(String name) {
return (String[]) getProperties(name, false).toArray();
}
/**
......@@ -253,7 +331,7 @@ public class XMLProperties {
*/
public Iterator getChildProperties(String name) {
String[] propName = parsePropertyName(name);
// Search for this property by traversing down the XML heirarchy,
// Search for this property by traversing down the XML hierarchy,
// stopping one short.
Element element = document.getRootElement();
for (int i = 0; i < propName.length - 1; i++) {
......@@ -265,17 +343,25 @@ public class XMLProperties {
}
}
// We found matching property, return values of the children.
Iterator iter = element.elementIterator(propName[propName.length - 1]);
Iterator<Element> iter = element.elementIterator(propName[propName.length - 1]);
ArrayList<String> props = new ArrayList<String>();
Element prop;
String value;
while (iter.hasNext()) {
props.add(((Element)iter.next()).getText());
prop = iter.next();
value = prop.getText();
// check to see if the property is marked as encrypted
if (JiveGlobals.isPropertyEncrypted(name) && Boolean.parseBoolean(prop.attribute(ENCRYPTED_ATTRIBUTE).getText())) {
value = JiveGlobals.getPropertyEncryptor().decrypt(value);
}
props.add(value);
}
return props.iterator();
}
/**
* Returns the value of the attribute of the given property name or <tt>null</tt>
* if it doesn't exist. Note, this
* if it doesn't exist.
*
* @param name the property name to lookup - ie, "foo.bar"
* @param attribute the name of the attribute, ie "id"
......@@ -287,7 +373,7 @@ public class XMLProperties {
return null;
}
String[] propName = parsePropertyName(name);
// Search for this property by traversing down the XML heirarchy.
// Search for this property by traversing down the XML hierarchy.
Element element = document.getRootElement();
for (String child : propName) {
element = element.element(child);
......@@ -304,6 +390,39 @@ public class XMLProperties {
return null;
}
/**
* Removes the given attribute from the XML document.
*
* @param name the property name to lookup - ie, "foo.bar"
* @param attribute the name of the attribute, ie "id"
* @return the value of the attribute of the given property or <tt>null</tt> if
* it did not exist.
*/
public String removeAttribute(String name, String attribute) {
if (name == null || attribute == null) {
return null;
}
String[] propName = parsePropertyName(name);
// Search for this property by traversing down the XML hierarchy.
Element element = document.getRootElement();
for (String child : propName) {
element = element.element(child);
if (element == null) {
// This node doesn't match this part of the property name which
// indicates this property doesn't exist so return empty array.
break;
}
}
String result = null;
if (element != null) {
// Get the attribute value and then remove the attribute
Attribute attr = element.attribute(attribute);
result = attr.getValue();
element.remove(attr);
}
return result;
}
/**
* Sets a property to an array of values. Multiple values matching the same property
* is mapped to an XML file as multiple elements containing each value.
......@@ -324,11 +443,11 @@ public class XMLProperties {
*/
public void setProperties(String name, List<String> values) {
String[] propName = parsePropertyName(name);
// Search for this property by traversing down the XML heirarchy,
// Search for this property by traversing down the XML hierarchy,
// stopping one short.
Element element = document.getRootElement();
for (int i = 0; i < propName.length - 1; i++) {
// If we don't find this part of the property in the XML heirarchy
// If we don't find this part of the property in the XML hierarchy
// we add it as a new node
if (element.element(propName[i]) == null) {
element.addElement(propName[i]);
......@@ -338,9 +457,9 @@ public class XMLProperties {
String childName = propName[propName.length - 1];
// We found matching property, clear all children.
List<Element> toRemove = new ArrayList<Element>();
Iterator iter = element.elementIterator(childName);
Iterator<Element> iter = element.elementIterator(childName);
while (iter.hasNext()) {
toRemove.add((Element) iter.next());
toRemove.add(iter.next());
}
for (iter = toRemove.iterator(); iter.hasNext();) {
element.remove((Element)iter.next());
......@@ -349,9 +468,9 @@ public class XMLProperties {
for (String value : values) {
Element childElement = element.addElement(childName);
if (value.startsWith("<![CDATA[")) {
Iterator it = childElement.nodeIterator();
Iterator<Node> it = childElement.nodeIterator();
while (it.hasNext()) {
Node node = (Node) it.next();
Node node = it.next();
if (node instanceof CDATA) {
childElement.remove(node);
break;
......@@ -360,7 +479,13 @@ public class XMLProperties {
childElement.addCDATA(value.substring(9, value.length()-3));
}
else {
childElement.setText(StringEscapeUtils.escapeXml(value));
String propValue = StringEscapeUtils.escapeXml(value);
// check to see if the property is marked as encrypted
if (JiveGlobals.isPropertyEncrypted(name)) {
propValue = JiveGlobals.getPropertyEncryptor().encrypt(propValue);
childElement.addAttribute(ENCRYPTED_ATTRIBUTE, "true");
}
childElement.setText(propValue);
}
}
saveProperties();
......@@ -371,6 +496,72 @@ public class XMLProperties {
PropertyEventDispatcher.dispatchEvent(name,
PropertyEventDispatcher.EventType.xml_property_set, params);
}
/**
* Adds the given value to the list of values represented by the property name.
* The property is created if it did not already exist.
*
* @param propertyName The name of the property list to change
* @param value The value to be added to the list
* @return True if the value was added to the list; false if the value was already present
*/
public boolean addToList(String propertyName, String value) {
List<String> properties = getProperties(propertyName, true);
boolean propertyWasAdded = properties.add(value);
if (propertyWasAdded) {
setProperties(propertyName, properties);
}
return propertyWasAdded;
}
/**
* Removes the given value from the list of values represented by the property name.
* The property is deleted if it no longer contains any values.
*
* @param propertyName The name of the property list to change
* @param value The value to be removed from the list
* @return True if the value was removed from the list; false if the value was not found
*/
public boolean removeFromList(String propertyName, String value) {
List<String> properties = getProperties(propertyName, true);
boolean propertyWasRemoved = properties.remove(value);
if (propertyWasRemoved) {
setProperties(propertyName, properties);
}
return propertyWasRemoved;
}
/**
* Returns a list of names for all properties found in the XML file.
*
* @return Names for all properties in the file
*/
public List<String> getAllPropertyNames() {
List<String> result = new ArrayList<String>();
for (String propertyName : getChildPropertyNamesFor(document.getRootElement(), "")) {
if (getProperty(propertyName) != null) {
result.add(propertyName);
}
}
return result;
}
private List<String> getChildPropertyNamesFor(Element parent, String parentName) {
List<String> result = new ArrayList<String>();
for (Element child : (Collection<Element>) parent.elements()) {
String childName = new StringBuilder(parentName)
.append(parentName.isEmpty() ? "" : ".")
.append(child.getName())
.toString();
if (!result.contains(childName)) {
result.add(childName);
result.addAll(getChildPropertyNamesFor(child, childName));
}
}
return result;
}
/**
* Return all children property names of a parent property as a String array,
......@@ -384,7 +575,7 @@ public class XMLProperties {
*/
public String[] getChildrenProperties(String parent) {
String[] propName = parsePropertyName(parent);
// Search for this property by traversing down the XML heirarchy.
// Search for this property by traversing down the XML hierarchy.
Element element = document.getRootElement();
for (String aPropName : propName) {
element = element.element(aPropName);
......@@ -426,10 +617,10 @@ public class XMLProperties {
propertyCache.put(name, value);
String[] propName = parsePropertyName(name);
// Search for this property by traversing down the XML heirarchy.
// Search for this property by traversing down the XML hierarchy.
Element element = document.getRootElement();
for (String aPropName : propName) {
// If we don't find this part of the property in the XML heirarchy
// If we don't find this part of the property in the XML hierarchy
// we add it as a new node
if (element.element(aPropName) == null) {
element.addElement(aPropName);
......@@ -449,7 +640,13 @@ public class XMLProperties {
element.addCDATA(value.substring(9, value.length()-3));
}
else {
element.setText(value);
String propValue = StringEscapeUtils.escapeXml(value);
// check to see if the property is marked as encrypted
if (JiveGlobals.isPropertyEncrypted(name)) {
propValue = JiveGlobals.getPropertyEncryptor().encrypt(propValue);
element.addAttribute(ENCRYPTED_ATTRIBUTE, "true");
}
element.setText(propValue);
}
// Write the XML properties to disk
saveProperties();
......@@ -471,7 +668,7 @@ public class XMLProperties {
propertyCache.remove(name);
String[] propName = parsePropertyName(name);
// Search for this property by traversing down the XML heirarchy.
// Search for this property by traversing down the XML hierarchy.
Element element = document.getRootElement();
for (int i = 0; i < propName.length - 1; i++) {
element = element.element(propName[i]);
......@@ -482,6 +679,9 @@ public class XMLProperties {
}
// Found the correct element to remove, so remove it...
element.remove(element.element(propName[propName.length - 1]));
if (element.elements().size() == 0) {
element.getParent().remove(element);
}
// .. then write to disk.
saveProperties();
......@@ -490,6 +690,30 @@ public class XMLProperties {
PropertyEventDispatcher.dispatchEvent(name, PropertyEventDispatcher.EventType.xml_property_deleted, params);
}
/**
* Convenience routine to migrate an XML property into the database
* storage method. Will check for the XML property being null before
* migrating.
*
* @param name the name of the property to migrate.
*/
public void migrateProperty(String name) {
if (getProperty(name) != null) {
if (JiveGlobals.getProperty(name) == null) {
Log.debug("JiveGlobals: Migrating XML property '"+name+"' into database.");
JiveGlobals.setProperty(name, getProperty(name));
deleteProperty(name);
}
else if (JiveGlobals.getProperty(name).equals(getProperty(name))) {
Log.debug("JiveGlobals: Deleting duplicate XML property '"+name+"' that is already in database.");
deleteProperty(name);
}
else if (!JiveGlobals.getProperty(name).equals(getProperty(name))) {
Log.warn("XML Property '"+name+"' differs from what is stored in the database. Please make property changes in the database instead of the configuration file.");
}
}
}
/**
* Builds the document XML model up based the given reader of XML data.
* @param in the input stream used to build the xml document
......@@ -545,7 +769,7 @@ public class XMLProperties {
}
}
// No errors occured, so delete the main file.
// No errors occurred, so delete the main file.
if (!error) {
// Delete the old file so we can replace it.
if (!file.delete()) {
......
......@@ -19,6 +19,7 @@
*/
package org.jivesoftware.util.cache;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
......@@ -70,7 +71,7 @@ public class CacheFactory {
private static String localCacheFactoryClass;
private static String clusteredCacheFactoryClass;
private static CacheFactoryStrategy cacheFactoryStrategy;
private static CacheFactoryStrategy cacheFactoryStrategy = new DefaultLocalCacheStrategy();
private static CacheFactoryStrategy localCacheFactoryStrategy;
private static CacheFactoryStrategy clusteredCacheFactoryStrategy;
private static Thread statsThread;
......@@ -440,6 +441,8 @@ public class CacheFactory {
clusteredCacheFactoryStrategy = (CacheFactoryStrategy) Class.forName(
clusteredCacheFactoryClass, true,
getClusteredCacheStrategyClassLoader()).newInstance();
} catch (NoClassDefFoundError e) {
log.warn("Clustered cache factory strategy " + clusteredCacheFactoryClass + " not found");
} catch (Exception e) {
log.warn("Clustered cache factory strategy " + clusteredCacheFactoryClass + " not found");
}
......@@ -626,10 +629,18 @@ public class CacheFactory {
}
PluginClassLoader pluginLoader = pluginManager.getPluginClassloader(plugin);
if (pluginLoader != null) {
if (log.isDebugEnabled()) {
StringBuffer pluginLoaderDetails = new StringBuffer("Clustering plugin class loader: ");
pluginLoaderDetails.append(pluginLoader.getClass().getName());
for (URL url : pluginLoader.getURLs()) {
pluginLoaderDetails.append("\n\t").append(url.toExternalForm());
}
log.debug(pluginLoaderDetails.toString());
}
return pluginLoader;
}
else {
log.debug("CacheFactory - Unable to find a Plugin that provides clustering support.");
log.warn("CacheFactory - Unable to find a Plugin that provides clustering support.");
return Thread.currentThread().getContextClassLoader();
}
}
......
package org.jivesoftware.util;
import java.util.UUID;
import junit.framework.TestCase;
import org.junit.Test;
public class AesEncryptorTest extends TestCase {
@Test
public void testEncryptionUsingDefaultKey() {
String test = UUID.randomUUID().toString();
Encryptor encryptor = new AesEncryptor();
String b64Encrypted = encryptor.encrypt(test);
assertFalse(test.equals(b64Encrypted));
assertEquals(test, encryptor.decrypt(b64Encrypted));
}
@Test
public void testEncryptionUsingCustomKey() {
String test = UUID.randomUUID().toString();
Encryptor encryptor = new AesEncryptor(UUID.randomUUID().toString());
String b64Encrypted = encryptor.encrypt(test);
assertFalse(test.equals(b64Encrypted));
assertEquals(test, encryptor.decrypt(b64Encrypted));
}
@Test
public void testEncryptionForEmptyString() {
String test = "";
Encryptor encryptor = new AesEncryptor();
String b64Encrypted = encryptor.encrypt(test);
assertFalse(test.equals(b64Encrypted));
assertEquals(test, encryptor.decrypt(b64Encrypted));
}
@Test
public void testEncryptionForNullString() {
String test = null;
Encryptor encryptor = new AesEncryptor();
String b64Encrypted = encryptor.encrypt(test);
assertNull(b64Encrypted);
}
}
package org.jivesoftware.util;
import java.util.UUID;
import junit.framework.TestCase;
import org.junit.Test;
public class BlowfishEncryptorTest extends TestCase {
@Test
public void testEncryptionUsingDefaultKey() {
String test = UUID.randomUUID().toString();
Encryptor encryptor = new Blowfish();
String b64Encrypted = encryptor.encrypt(test);
assertFalse(test.equals(b64Encrypted));
assertEquals(test, encryptor.decrypt(b64Encrypted));
}
@Test
public void testEncryptionUsingCustomKey() {
String test = UUID.randomUUID().toString();
Encryptor encryptor = new Blowfish(UUID.randomUUID().toString());
String b64Encrypted = encryptor.encrypt(test);
assertFalse(test.equals(b64Encrypted));
assertEquals(test, encryptor.decrypt(b64Encrypted));
}
@Test
public void testEncryptionForEmptyString() {
String test = "";
Encryptor encryptor = new Blowfish();
String b64Encrypted = encryptor.encrypt(test);
assertFalse(test.equals(b64Encrypted));
assertEquals(test, encryptor.decrypt(b64Encrypted));
}
@Test
public void testEncryptionForNullString() {
String test = null;
Encryptor encryptor = new Blowfish();
String b64Encrypted = encryptor.encrypt(test);
assertNull(b64Encrypted);
}
}
src/web/images/add-16x16.gif

615 Bytes | W: | H:

src/web/images/add-16x16.gif

614 Bytes | W: | H:

src/web/images/add-16x16.gif
src/web/images/add-16x16.gif
src/web/images/add-16x16.gif
src/web/images/add-16x16.gif
  • 2-up
  • Swipe
  • Onion skin
......@@ -63,6 +63,7 @@
String propName = ParamUtils.getParameter(request,"propName");
String propValue = ParamUtils.getParameter(request,"propValue",true);
boolean edit = ParamUtils.getBooleanParameter(request,"edit");
boolean encrypt = ParamUtils.getBooleanParameter(request,"encrypt");
boolean save = request.getParameter("save") != null;
boolean delete = ParamUtils.getBooleanParameter(request,"del");
......@@ -81,6 +82,16 @@
}
}
if (encrypt) {
if (propName != null) {
JiveGlobals.setPropertyEncrypted(propName, true);
// Log the event
webManager.logEvent("encrypted server property "+propName, null);
response.sendRedirect("server-properties.jsp?encryptsuccess=true");
return;
}
}
Map<String, String> errors = new HashMap<String, String>();
if (save) {
if (propName == null || "".equals(propName.trim()) || propName.startsWith("\"")) {
......@@ -93,6 +104,7 @@
errors.put("propValueLength","");
}
if (errors.size() == 0) {
JiveGlobals.setPropertyEncrypted(propName, encrypt);
JiveGlobals.setProperty(propName, propValue);
// Log the event
webManager.logEvent("set server property "+propName, propName+" = "+propValue);
......@@ -166,6 +178,19 @@
</table>
</div><br>
<% } else if ("true".equals(request.getParameter("encryptsuccess"))) { %>
<div class="jive-success">
<table cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr><td class="jive-icon"><img src="images/success-16x16.gif" width="16" height="16" border="0" alt=""></td>
<td class="jive-icon-label">
<fmt:message key="server.properties.encrypted" />
</td></tr>
</tbody>
</table>
</div><br>
<% } %>
<% if (edit) { %>
......@@ -205,8 +230,17 @@ function doedit(propName) {
document.propform.action = document.propform.action + '#edit';
document.propform.submit();
}
function doencrypt(propName) {
var doencrypt = confirm('<fmt:message key="server.properties.encrypt_confirm" />');
if (doencrypt) {
document.propform.propName.value = propName;
document.propform.encrypt.value = 'true';
document.propform.action = document.propform.action + '#encrypt';
document.propform.submit();
}
}
function dodelete(propName) {
var dodelete = confirm('Are you sure you want to delete this property?');
var dodelete = confirm('<fmt:message key="server.properties.delete_confirm" />');
if (dodelete) {
document.propform.propName.value = propName;
document.propform.del.value = 'true';
......@@ -221,6 +255,7 @@ function dodelete(propName) {
<form action="server-properties.jsp" method="post" name="propform">
<input type="hidden" name="edit" value="">
<input type="hidden" name="encrypt" value="">
<input type="hidden" name="del" value="">
<input type="hidden" name="propName" value="">
......@@ -239,6 +274,7 @@ function dodelete(propName) {
<th nowrap><fmt:message key="server.properties.name" /></th>
<th nowrap><fmt:message key="server.properties.value" /></th>
<th style="text-align:center;"><fmt:message key="server.properties.edit" /></th>
<th style="text-align:center;"><fmt:message key="server.properties.encrypt" /></th>
<th style="text-align:center;"><fmt:message key="global.delete" /></th>
</tr>
</thead>
......@@ -269,9 +305,8 @@ function dodelete(propName) {
</td>
<td>
<div class="hidebox" style="width:300px;">
<% if (n.toLowerCase().indexOf("passwd") > -1 ||
n.toLowerCase().indexOf("password") > -1 ||
n.toLowerCase().indexOf("cookiekey") > -1) { %>
<% if (JiveGlobals.isPropertyEncrypted(n) ||
JiveGlobals.isPropertySensitive(n)) { %>
<span style="color:#999;"><i>hidden</i></span>
<% } else { %>
<span title="<%= ("".equals(v) ? "&nbsp;" : v) %>"><%= ("".equals(v) ? "&nbsp;" : v) %></span>
......@@ -283,6 +318,15 @@ function dodelete(propName) {
alt="<fmt:message key="server.properties.alt_edit" />" border="0"></a
>
</td>
<td align="center"><%
if (!JiveGlobals.isPropertyEncrypted(n)) { %>
<a href="#" onclick="doencrypt('<%= StringUtils.replace(StringUtils.escapeHTMLTags(n),"'","''") %>');" >
<img src="images/add-16x16.gif" width="16" height="16" alt="<fmt:message key="server.properties.alt_encrypt" />" border="0"></a><%
} else { %>
<img src="images/lock.gif" width="16" height="16" alt="<fmt:message key="server.properties.alt_encrypted" />" border="0"><%
} %>
</td>
<td align="center"><a href="#" onclick="return dodelete('<%= StringUtils.replace(StringUtils.escapeHTMLTags(n),"'","''") %>');"
><img src="images/delete-16x16.gif" width="16" height="16"
alt="<fmt:message key="server.properties.alt_delete" />" border="0"></a
......@@ -345,6 +389,7 @@ function dodelete(propName) {
<fmt:message key="server.properties.value" />:
</td>
<td>
<% if (JiveGlobals.isPropertyEncrypted(propName) || JiveGlobals.isPropertySensitive(propName)) { propValue = null; } %>
<textarea cols="45" rows="5" name="propValue" wrap="virtual"><%= (propValue != null ? StringUtils.escapeHTMLTags(propValue, false) : "") %></textarea>
<% if (errors.containsKey("propValue")) { %>
......@@ -358,6 +403,15 @@ function dodelete(propName) {
<% } %>
</td>
</tr>
<tr valign="top">
<td>
<fmt:message key="server.properties.encryption" />:
</td>
<td>
<input type="radio" name="encrypt" value="true" <%= JiveGlobals.isPropertyEncrypted(propName) ? "checked" : "" %> /><fmt:message key="server.properties.encrypt_property_true"/><br/>
<input type="radio" name="encrypt" value="false" <%= JiveGlobals.isPropertyEncrypted(propName) ? "" : "checked" %>/><fmt:message key="server.properties.encrypt_property_false"/>
</td>
</tr>
</tbody>
<tfoot>
<tr>
......
......@@ -29,6 +29,8 @@
int embeddedPort = ParamUtils.getIntParameter(request, "embeddedPort", Integer.MIN_VALUE);
int securePort = ParamUtils.getIntParameter(request, "securePort", Integer.MIN_VALUE);
boolean sslEnabled = ParamUtils.getBooleanParameter(request, "sslEnabled", true);
String encryptionAlgorithm = ParamUtils.getParameter(request, "encryptionAlgorithm");
String encryptionKey = ParamUtils.getParameter(request, "encryptionKey");
boolean doContinue = request.getParameter("continue") != null;
......@@ -55,6 +57,14 @@
else if (securePort < 0) {
securePort = -1;
}
if (encryptionKey != null) {
// ensure the same key value was provided twice
String repeat = ParamUtils.getParameter(request, "encryptionKey1");
if (!encryptionKey.equals(repeat)) {
errors.put("encryptionKey", "encryptionKey");
}
}
} else {
embeddedPort = -1;
securePort = -1;
......@@ -73,6 +83,9 @@
xmlSettings.put("adminConsole.securePort", Integer.toString(securePort));
session.setAttribute("xmlSettings", xmlSettings);
JiveGlobals.setupPropertyEncryptionAlgorithm(encryptionAlgorithm);
JiveGlobals.setupPropertyEncryptionKey(encryptionKey);
// Successful, so redirect
response.sendRedirect("setup-datasource-settings.jsp");
return;
......@@ -166,6 +179,31 @@
<% } %>
</td>
</tr>
<tr valign="top">
<td width="1%" nowrap align="right">
<fmt:message key="setup.host.settings.encryption_algorithm" />
</td>
<td width="99%">
<span class="jive-setup-helpicon" onmouseover="domTT_activate(this, event, 'content', '<fmt:message key="setup.host.settings.encryption_algorithm_info" />', 'styleClass', 'jiveTooltip', 'trail', true, 'delay', 300, 'lifetime', 8000);"></span><br /><br />
<input type="radio" name="encryptionAlgorithm" value="Blowfish" checked><fmt:message key="setup.host.settings.encryption_blowfish" /><br /><br />
<input type="radio" name="encryptionAlgorithm" value="AES"><fmt:message key="setup.host.settings.encryption_aes" /><br /><br />
</td>
</tr>
<tr valign="top">
<td width="1%" nowrap align="right">
<fmt:message key="setup.host.settings.encryption_key" />
</td>
<td width="99%">
<input type="password" size="50" name="encryptionKey" /><br /><br />
<input type="password" size="50" name="encryptionKey1" />
<span class="jive-setup-helpicon" onmouseover="domTT_activate(this, event, 'content', '<fmt:message key="setup.host.settings.encryption_key_info" />', 'styleClass', 'jiveTooltip', 'trail', true, 'delay', 300, 'lifetime', 8000);"></span>
<% if (errors.get("encryptionKey") != null) { %>
<span class="jive-error-text">
<fmt:message key="setup.host.settings.encryption_key_invalid" />
</span>
<% } %>
</td>
</tr>
<% } %>
</table>
......
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