Pages

Monday, May 21, 2012

Masking Arbitrary Properties in JBoss

JBoss (AS and EAP) Provides various ways of masking or encrypting plain text passwords so as to hide them, not only from attackers, but also from employees who may have read access to a box. Unfortunately, many of these masking solutions are very specific to a certain password or configuration item, and are not widely applicable. Furthermore, many of the masking or encryption techniques, at some level, depend on having static encryption keys or passwords stored in java classes. As JBoss is open source, it would not be difficult to locate the java file containing the key, and use that to decrypt the password.
While I have yet to see a solution to this that I love, I did come across a method that is both widely applicable, and simple enough that I was able to customize it for some added security (here). This method involves a jboss service (SAR) that is used to encrypt the properties (via a command line call) and then decrypt them and load them in memory once it gets deployed to jboss.
NOTE: This method comes from the community and is NOT recommended, nor supported by Red Hat. If you are using AS, feel free to use this method for whatever passwords/properties you would like. If you are using EAP (paying for support) I would recommend using the officially recommended methods from the Red Hat docs, where available.
First, let's take a look at the java code:
public class EncryptSystemPropertiesService implements
  EncryptSystemPropertiesServiceMBean {

 private Logger log = Logger.getLogger(this.getClass());
 private String encryptedSystemPropertiesFilename;
 private String encryptionKeyFilename;
 private static String encryptionKey;
 
 public static void main(String[] args) throws Exception {
  if(args.length < 2) {
   System.err.println("Usage:  java org.jboss.example.EncryptSystemPropertiesService [encryption key] [string to encode]");
   System.exit(0);
  }
  System.out.println(encode(args[0], args[1]));
 }

 public void start() {
  log.info("Starting EncryptSystemPropertiesService");
  loadEncryptionKey();
  loadSystemProperties();
 }

 public void stop() {
  log.info("Stopping EncryptSystemPropertiesService");
 }
 
 // GETTERS and SETTERS omitted

 private void loadEncryptionKey() {
  log.info("Loading system properties from: " + encryptionKeyFilename);
  
  File file = null;
  
  try {
   // This will look for the filename in the
   // $JBOSS_HOME/server//conf/ directory
   URL url = this.getClass().getClassLoader().getResource(encryptionKeyFilename);

   // Load the properties
   file = new File(url.getPath());
   Properties properties = new Properties();
   InputStream is = new FileInputStream(file);
   properties.load(is);

   // Get encryption key from temporary properties file
   setEncryptionKey(properties.getProperty("encryption-key"));
  } catch (Exception e) {
   if(file == null) {
    log.error("Failed to loadEncryptionKey for: " + encryptionKeyFilename, e);
   } else {
    log.error("Failed to loadEncryptionKey for: " + file.getAbsolutePath(), e);
   }
  }
 }


 private void loadSystemProperties() {
  log.info("Loading system properties from: " + encryptedSystemPropertiesFilename);
  
  File file = null;
  
  try {
   // This will look for the filename in the
   // $JBOSS_HOME/server//conf/ directory
   URL url = this.getClass().getClassLoader().getResource(encryptedSystemPropertiesFilename);

   // Load the properties
   file = new File(url.getPath());
   Properties properties = new Properties();
   InputStream is = new FileInputStream(file);
   properties.load(is);

   // Loop though the properties in the file and decrypt the values
   for (Enumeration e = properties.propertyNames(); e
     .hasMoreElements();) {
    String key = (String) e.nextElement();
    String encryptedValue = properties.getProperty(key);
    String value = decode(encryptedValue);

    // Set the decrypted value in the System properties
    System.setProperty(key, value);
   }
  } catch (Exception e) {
   if(file == null) {
    log.error("Failed to loadSystemProperties for: " + encryptedSystemPropertiesFilename, e);
   } else {
    log.error("Failed to loadSystemProperties for: " + file.getAbsolutePath(), e);
   }
  }
 }

 private static String decode(String secret) throws Exception {
  byte[] kbytes = getEncryptionKey().getBytes();
  SecretKeySpec key = new SecretKeySpec(kbytes, "Blowfish");
  BigInteger n = new BigInteger(secret, 16);
  byte[] encoding = n.toByteArray();
  Cipher cipher = Cipher.getInstance("Blowfish");
  cipher.init(Cipher.DECRYPT_MODE, key);
  byte[] decode = cipher.doFinal(encoding);
  return new String(decode);
 }

 private static String encode(String salt, String secret) throws Exception {
  byte[] kbytes = salt.getBytes();
  SecretKeySpec key = new SecretKeySpec(kbytes, "Blowfish");
  Cipher cipher = Cipher.getInstance("Blowfish");
  cipher.init(Cipher.ENCRYPT_MODE, key);
  byte[] encoding = cipher.doFinal(secret.getBytes());
  BigInteger n = new BigInteger(encoding);
  return n.toString(16);
 }

}
This code provides two functions. The first is to provide a command line method for encrypting a password:
java -cp $JBOSS_HOME/lib/log4j.jar:./EncryptSystemPropertiesService.sar org.jboss.example.EncryptSystemPropertiesService myEncryptionKey myProperty
The second is to provide a JBoss service that, on startup, decrypts the properties, and loads them into memory. This is assuming they have been entered into a properties file called $JBOSS_HOME/server/$PROFILE/conf/encrypted-properties.properties and the encryption key is saved to $JBOSS_HOME/server$PROFILE/conf/.encryption-key.properties. These filenames are defined in jboss-service.xml.
Now, despite the fact that we have avoided ever displaying our sensitive properties in plain text, we still store the encryption key. While this does add an extra layer of obscurity, it may not be enough of a deterent for more highly secure application. So, to add another layer of security, I have automated this entire process via a script that does the following:

  1. Generate the encrypted properties
  2. Write encrypted properties and encryption key to their respective properties files
  3. Start JBoss
  4. Query for our encryption service to start (via twiddle)
  5. Delete both properties files once we know that they have been loaded into memory.

Here is the script:

#!/bin/bash

#-----------------------------------------------------------------------#
#   Custom JBoss Run Script    #
#-----------------------------------------------------------------------#

# Takes the following arguments:
# ./jbossRun.sh [encryption key] [truststore-password]

#Properties
JBOSS_HOME='/home/eric/appservers/jboss-eap-5.1/jboss-as'
PROFILE="default"

if [ $# -ne "6" ]; then
 echo "Syntax: ./jbossRun.sh [encryption key] [property-name] [property-value] [username] [password] [server hostname]"
 exit
fi

echo "Starting..."

ENCRYPTION_KEY=$1

PROPERTY_NAME=$2

PROPERTY_VALUE=$3

USERNAME=$4

PASSWORD=$5

SERVER_HOST=$6

ENC_PROPERTY_VALUE=`java -cp $JBOSS_HOME/common/lib/log4j.jar:$JBOSS_HOME/server/$PROFILE/deploy/EncryptSystemPropertiesService.sar org.jboss.example.EncryptSystemPropertiesService $ENCRYPTION_KEY $PROPERTY_VALUE`

# Create file containing keystore password system variable

echo "$PROPERTY_NAME=$ENC_PROPERTY_VALUE" > $JBOSS_HOME/server/$PROFILE/conf/encrypted-properties.properties

# Create temporary file containing encryption key

echo "encryption-key=$ENCRYPTION_KEY" > $JBOSS_HOME/server/$PROFILE/conf/.encryption-key.properties

#ls -a $JBOSS_HOME/server/$PROFILE/conf/
#echo

$JBOSS_HOME/bin/run.sh &

sleep 4

breaker=0

while [ $breaker -eq 0 ]; do
 sleep 1
 results=`$JBOSS_HOME/bin/twiddle.sh -s $SERVER_HOST -u $USERNAME -p $PASSWORD query EncryptSystemProperties:service=EncryptSystemProperties`
 if [ "$results" == "EncryptSystemProperties:service=EncryptSystemProperties" ]; then
  breaker=1
 fi
done

rm $JBOSS_HOME/server/$PROFILE/conf/.encryption-key.properties
rm $JBOSS_HOME/server/$PROFILE/conf/encrypted-properties.properties

#ls -a $JBOSS_HOME/server/$PROFILE/conf/
#echo

echo "JBoss Started Securely"
Now our properties have been loaded into memory, having only existed in readable form for a few seconds.
EcnryptSystemPropertiesService
JBoss Run Script

No comments:

Post a Comment