org.owasp.esapi.reference.crypto.ReferenceEncryptedProperties Maven / Gradle / Ivy
/**
* OWASP Enterprise Security API (ESAPI)
*
* This file is part of the Open Web Application Security Project (OWASP)
* Enterprise Security API (ESAPI) project. For details, please see
* http://www.owasp.org/index.php/ESAPI.
*
* Copyright (c) 2007 - The OWASP Foundation
*
* The ESAPI is published by OWASP under the BSD license. You should read and accept the
* LICENSE before you use, modify, and/or redistribute this software.
*
* @author Jeff Williams Aspect Security
* @created 2007
*/
package org.owasp.esapi.reference.crypto;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Set;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.EncryptedProperties;
import org.owasp.esapi.Logger;
import org.owasp.esapi.crypto.CipherText;
import org.owasp.esapi.crypto.PlainText;
import org.owasp.esapi.errors.EncryptionRuntimeException;
/**
* Reference implementation of the {@code EncryptedProperties} interface. This
* implementation wraps a normal properties file, and creates surrogates for the
* {@code getProperty} and {@code setProperty} methods that perform encryption
* and decryption based on {@code Encryptor}.
*
* This implementation differs from {@code DefaultEncryptedProperties} in that
* it actually extends from {@code java.util.Properties} for applications that need an
* instance of that class. In order to do so, the {@code getProperty} and
* {@code setProperty} methods were modified to throw {@code EncryptionRuntimeException}
* instead of {@code EncryptionException}.
*
* @author August Detlefsen (augustd at codemagi dot com)
* CodeMagi, Inc.
* @author [email protected]
* @since October 8, 2010
* @see org.owasp.esapi.EncryptedProperties
* @see org.owasp.esapi.reference.crypto.DefaultEncryptedProperties
*/
public class ReferenceEncryptedProperties extends java.util.Properties implements EncryptedProperties {
/**
* serverVersionUID; use format of YYYYMMDD.
*/
private static final long serialVersionUID = 20120718L;
/** The logger. */
private final Logger logger = ESAPI.getLogger(this.getClass());
private static final String[] GET_ERROR_MESSAGES = new String[]{
": failed decoding from base64",
": failed to deserialize properly",
": failed to decrypt properly"
};
private static final String[] SET_ERROR_MESSAGES = new String[]{
": failed to encrypt properly",
": failed to serialize correctly",
": failed to base64-encode properly",
": failed to set base64-encoded value as property. Illegal key name?"
};
/**
* Instantiates a new encrypted properties.
*/
public ReferenceEncryptedProperties() {
super();
}
public ReferenceEncryptedProperties(Properties defaults) {
super();
for (Object oKey : defaults.keySet()) {
String key = (oKey instanceof String) ? (String)oKey : oKey.toString();
String value = defaults.getProperty(key);
this.setProperty(key, value);
}
}
/**
* {@inheritDoc}
*
* @throws EncryptionRuntimeException Thrown if decryption fails.
*/
@Override
public synchronized String getProperty(String key) throws EncryptionRuntimeException {
int progressMark = 0;
try {
String encryptedValue = super.getProperty(key);
if(encryptedValue==null)
return null;
progressMark = 0;
byte[] serializedCiphertext = ESAPI.encoder().decodeFromBase64(encryptedValue);
progressMark++;
CipherText restoredCipherText = CipherText.fromPortableSerializedBytes(serializedCiphertext);
progressMark++;
PlainText plaintext = ESAPI.encryptor().decrypt(restoredCipherText);
return plaintext.toString();
} catch (Exception e) {
throw new EncryptionRuntimeException("Property retrieval failure",
"Couldn't retrieve encrypted property for property " + key +
GET_ERROR_MESSAGES[progressMark], e);
}
}
/**
* {@inheritDoc}
*
* @throws EncryptionRuntimeException Thrown if decryption fails.
*/
@Override
public synchronized String getProperty(String key, String defaultValue) throws EncryptionRuntimeException {
String value = getProperty(key);
if (value == null) return defaultValue;
return value;
}
/**
* {@inheritDoc}
*
* @throws EncryptionRuntimeException Thrown if encryption fails.
*/
@Override
public synchronized String setProperty(String key, String value) throws EncryptionRuntimeException {
int progressMark = 0;
try {
if ( key == null ) {
throw new NullPointerException("Property name may not be null.");
}
if ( value == null ) {
throw new NullPointerException("Property value may not be null.");
}
// NOTE: Not backward compatible w/ ESAPI 1.4.
PlainText pt = new PlainText(value);
CipherText ct = ESAPI.encryptor().encrypt(pt);
progressMark++;
byte[] serializedCiphertext = ct.asPortableSerializedByteArray();
progressMark++;
String b64str = ESAPI.encoder().encodeForBase64(serializedCiphertext, false);
progressMark++;
return (String)super.put(key, b64str);
} catch (Exception e) {
throw new EncryptionRuntimeException("Property setting failure",
"Couldn't set encrypted property " + key +
SET_ERROR_MESSAGES[progressMark], e);
}
}
/**
* {@inheritDoc}
* @throws IOException Thrown if input stream invalid or does not
* correspond to Java properties file format.
*/
@Override
public void load(InputStream in) throws IOException {
super.load(in);
logger.trace(Logger.SECURITY_SUCCESS, "Encrypted properties loaded successfully");
}
/**
* {@inheritDoc}
*
* For JDK 1.5 compatibility, this method has been overridden convert the Reader
* into an InputStream and call the superclass constructor.
*
* @throws IOException Thrown if {@code Reader} input stream invalid or does not
* correspond to Java properties file format.
*/
public void load(Reader in) throws IOException {
if (in == null) return;
//read from the reader into a StringBuffer
char[] cbuf = new char[65536];
BufferedReader buff = new BufferedReader(in);
StringBuilder contents = new StringBuilder();
int read_this_time = 0;
while (read_this_time != -1) {
read_this_time = buff.read(cbuf, 0, 65536);
if (read_this_time > 0) contents.append(cbuf, 0, read_this_time);
}
//create a new InputStream from the StringBuffer
InputStream is = new ByteArrayInputStream(contents.toString().getBytes());
super.load(is);
logger.trace(Logger.SECURITY_SUCCESS, "Encrypted properties loaded successfully");
}
/**
* This method has been overridden to throw an {@code UnsupportedOperationException}
*/
@Override
public void list(PrintStream out) {
throw new UnsupportedOperationException("This method has been removed for security.");
}
/**
* This method has been overridden to throw an {@code UnsupportedOperationException}
*/
@Override
public void list(PrintWriter out) {
throw new UnsupportedOperationException("This method has been removed for security.");
}
/**
* This method has been overridden to throw an {@code UnsupportedOperationException}
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Collection values() {
throw new UnsupportedOperationException("This method has been removed for security.");
}
/**
* This method has been overridden to throw an {@code UnsupportedOperationException}
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Set entrySet() {
throw new UnsupportedOperationException("This method has been removed for security.");
}
/**
* This method has been overridden to throw an {@code UnsupportedOperationException}
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Enumeration elements() {
throw new UnsupportedOperationException("This method has been removed for security.");
}
/**
* This method has been overridden to only accept Strings for key and value, and to encrypt
* those Strings before storing them. Outside classes should always use {@code setProperty}
* to add values to the Properties map. If an outside class does erroneously call this method
* with non-String parameters an {@code IllegalArgumentException} will be thrown.
*
* @param key A String key to add
* @param value A String value to add
* @return The old value associated with the specified key, or {@code null}
* if the key did not exist.
*/
@Override
public synchronized Object put(Object key, Object value) {
//if java.util.Properties is calling this method, just forward to the implementation in
//the superclass (java.util.Hashtable)
Throwable t = new Throwable();
for (StackTraceElement trace : t.getStackTrace()) {
if ("java.util.Properties".equals(trace.getClassName()) ) return super.put(key, value);
}
//otherwise, if both arguments are Strings, encrypt and store them
if (key instanceof String && value instanceof String) return setProperty((String)key, (String)value);
//other Object types are not allowed
throw new IllegalArgumentException("This method has been overridden to only accept Strings for key and value.");
}
/**
* This method has been overridden to not print out the keys and values stored in this properties file.
*
* @return The minimal String representation of this class, as per java.lang.Object.
*/
@Override
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
}