org.apache.myfaces.shared_tomahawk.util.StateUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tomahawk12 Show documentation
Show all versions of tomahawk12 Show documentation
JSF components and utilities that can be used with any JSF implementation.
This library is based on the JSF1.1 version of Tomahawk, but with minor source code and build
changes to take advantage of JSF1.2 features. A JSF1.2 implementation is required to use this
version of the Tomahawk library.
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.myfaces.shared_tomahawk.util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.security.AccessController;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Random;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.faces.FacesException;
import javax.faces.application.ViewExpiredException;
import javax.faces.context.ExternalContext;
import javax.servlet.ServletContext;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
import org.apache.myfaces.shared_tomahawk.util.serial.SerialFactory;
/**
* This Class exposes a handful of methods related to encryption,
* compression and serialization.
*
*
* - ISO-8859-1 is the character set used.
* - GZIP is used for all compression/decompression.
* - Base64 is used for all encoding and decoding.
* - DES is the default encryption algorithm
* - ECB is the default mode
* - PKCS5Padding is the default padding
* - HmacSHA1 is the default MAC algorithm
* - The default algorithm can be overridden using the
* org.apache.myfaces.ALGORITHM parameter
* - The default mode and padding can be overridden using the
* org.apache.myfaces.ALGORITHM.PARAMETERS parameter
* - This class has not been tested with modes other than ECB and CBC
* - An initialization vector can be specified via the
* org.apache.myfaces.ALGORITHM.IV parameter
* - The default MAC algorithm can be overridden using the
* org.apache.myfaces.MAC_ALGORITHM parameter
*
*
* The secret is interpretted as base 64 encoded. In other
* words, if your secret is "76543210", you would put "NzY1NDMyMTA=" in
* the deployment descriptor. This is needed so that key values are not
* limited to just values composed of printable characters.
*
* If you are using CBC mode encryption, you must specify an
* initialization vector.
*
* If you are using the AES algorithm and getting a SecurityException
* complaining about keysize, you most likely need to get the unlimited
* strength jurisdiction policy files from a place like
* http://java.sun.com/j2se/1.4.2/download.html .
*
* @author Dennis C. Byrne
* @see org.apache.myfaces.webapp.StartupServletContextListener
*/
public final class StateUtils {
private static final Log log = LogFactory.getLog(StateUtils.class);
public static final String ZIP_CHARSET = "ISO-8859-1";
public static final String DEFAULT_ALGORITHM = "DES";
public static final String DEFAULT_ALGORITHM_PARAMS = "ECB/PKCS5Padding";
public static final String INIT_PREFIX = "org.apache.myfaces.";
/**
* Indicate if the view state is encrypted or not. By default, encryption is enabled.
*/
@JSFWebConfigParam(name="org.apache.myfaces.USE_ENCRYPTION",since="1.1",defaultValue="true",expectedValues="true,false")
public static final String USE_ENCRYPTION = INIT_PREFIX + "USE_ENCRYPTION";
/**
* Defines the secret (Base64 encoded) used to initialize the secret key
* for encryption algorithm. See MyFaces wiki/web site documentation
* for instructions on how to configure an application for
* different encryption strengths.
*/
@JSFWebConfigParam(name="org.apache.myfaces.SECRET",since="1.1")
public static final String INIT_SECRET = INIT_PREFIX + "SECRET";
/**
* Indicate the encryption algorithm used for encrypt the view state.
*/
@JSFWebConfigParam(name="org.apache.myfaces.ALGORITHM",since="1.1",defaultValue="DES")
public static final String INIT_ALGORITHM = INIT_PREFIX + "ALGORITHM";
/**
* If is set to "false", the secret key used for encryption algorithm is not cached. This is used
* when the returned SecretKey for encryption algorithm is not thread safe.
*/
@JSFWebConfigParam(name="org.apache.myfaces.SECRET.CACHE",since="1.1")
public static final String INIT_SECRET_KEY_CACHE = INIT_SECRET + ".CACHE";
/**
* Defines the initialization vector (Base64 encoded) used for the encryption algorithm
*/
@JSFWebConfigParam(name="org.apache.myfaces.ALGORITHM.IV",since="1.1")
public static final String INIT_ALGORITHM_IV = INIT_ALGORITHM + ".IV";
/**
* Defines the default mode and padding used for the encryption algorithm
*/
@JSFWebConfigParam(name="org.apache.myfaces.ALGORITHM.PARAMETERS",since="1.1",defaultValue="ECB/PKCS5Padding")
public static final String INIT_ALGORITHM_PARAM = INIT_ALGORITHM + ".PARAMETERS";
/**
* Defines the factory class name using for serialize/deserialize the view state returned
* by state manager into a byte array. The expected class must implement
* org.apache.myfaces.shared_tomahawk.util.serial.SerialFactory interface.
*/
@JSFWebConfigParam(name="org.apache.myfaces.SERIAL_FACTORY", since="1.1")
public static final String SERIAL_FACTORY = INIT_PREFIX + "SERIAL_FACTORY";
/**
* Indicate if the view state should be compressed before encrypted(optional) and encoded
*/
@JSFWebConfigParam(name="org.apache.myfaces.COMPRESS_STATE_IN_CLIENT",since="1.1",defaultValue="false",expectedValues="true,false")
public static final String COMPRESS_STATE_IN_CLIENT = INIT_PREFIX + "COMPRESS_STATE_IN_CLIENT";
public static final String DEFAULT_MAC_ALGORITHM = "HmacSHA1";
/**
* Indicate the algorithm used to calculate the Message Authentication Code that is
* added to the view state.
*/
@JSFWebConfigParam(name="org.apache.myfaces.MAC_ALGORITHM",defaultValue="HmacSHA1")
public static final String INIT_MAC_ALGORITHM = "org.apache.myfaces.MAC_ALGORITHM";
/**
* Define the initialization code that are used to initialize the secret key used
* on the Message Authentication Code algorithm
*/
@JSFWebConfigParam(name="org.apache.myfaces.MAC_SECRET")
public static final String INIT_MAC_SECRET = "org.apache.myfaces.MAC_SECRET";
/**
* If is set to "false", the secret key used for MAC algorithm is not cached. This is used
* when the returned SecretKey for mac algorithm is not thread safe.
*/
@JSFWebConfigParam(name="org.apache.myfaces.MAC_SECRET.CACHE")
public static final String INIT_MAC_SECRET_KEY_CACHE = "org.apache.myfaces.MAC_SECRET.CACHE";
/** Utility class, do not instatiate */
private StateUtils()
{
//nope
}
private static void testConfiguration(ExternalContext ctx){
String algorithmParams = ctx.getInitParameter(INIT_ALGORITHM_PARAM);
if (algorithmParams == null)
{
algorithmParams = ctx.getInitParameter(INIT_ALGORITHM_PARAM.toLowerCase());
}
String iv = ctx.getInitParameter(INIT_ALGORITHM_IV);
if (iv == null)
{
iv = ctx.getInitParameter(INIT_ALGORITHM_IV.toLowerCase());
}
if (algorithmParams != null && algorithmParams.startsWith("CBC") )
{
if(iv == null)
throw new FacesException(INIT_ALGORITHM_PARAM +
" parameter has been set with CBC mode," +
" but no initialization vector has been set " +
" with " + INIT_ALGORITHM_IV);
}
}
public static boolean enableCompression(ExternalContext ctx)
{
if(ctx == null)
throw new NullPointerException("ExternalContext ctx");
return "true".equals(ctx.getInitParameter(COMPRESS_STATE_IN_CLIENT));
}
public static boolean isSecure(ExternalContext ctx)
{
if(ctx == null)
throw new NullPointerException("ExternalContext ctx");
return ! "false".equals(ctx.getInitParameter(USE_ENCRYPTION));
}
/**
* This fires during the Render Response phase, saving state.
*/
public static final String construct(Object object, ExternalContext ctx){
byte[] bytes = getAsByteArray(object, ctx);
if( enableCompression(ctx) )
bytes = compress(bytes);
if(isSecure(ctx))
bytes = encrypt(bytes, ctx);
bytes = encode(bytes);
try
{
return new String(bytes, ZIP_CHARSET);
}
catch (UnsupportedEncodingException e)
{
throw new FacesException(e);
}
}
/**
* Performs serialization with the serialization provider created by the
* SerialFactory.
*
* @param object
* @param ctx
* @return
*/
public static final byte[] getAsByteArray(Object object, ExternalContext ctx)
{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// get the Factory that was instantiated @ startup
SerialFactory serialFactory = (SerialFactory) ctx.getApplicationMap().get(SERIAL_FACTORY);
if(serialFactory == null)
throw new NullPointerException("serialFactory");
try
{
ObjectOutputStream writer = serialFactory.getObjectOutputStream(outputStream);
writer.writeObject(object);
byte[] bytes = outputStream.toByteArray();
writer.close();
outputStream.close();
writer = null;
outputStream = null;
return bytes;
}
catch (IOException e)
{
throw new FacesException(e);
}
}
public static byte[] encrypt(byte[] insecure, ExternalContext ctx)
{
if (ctx == null)
throw new NullPointerException("ExternalContext ctx");
testConfiguration(ctx);
SecretKey secretKey = (SecretKey) getSecret(ctx);
String algorithm = findAlgorithm(ctx);
String algorithmParams = findAlgorithmParams(ctx);
byte[] iv = findInitializationVector(ctx);
SecretKey macSecretKey = (SecretKey) getMacSecret(ctx);
String macAlgorithm = findMacAlgorithm(ctx);
try
{
// keep local to avoid threading issue
Mac mac = Mac.getInstance(macAlgorithm);
mac.init(macSecretKey);
Cipher cipher = Cipher.getInstance(algorithm + "/" + algorithmParams);
if (iv != null)
{
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
}
else
{
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
}
if (log.isDebugEnabled())
{
log.debug("encrypting w/ " + algorithm + "/" + algorithmParams);
}
//EtM Composition Approach
int macLenght = mac.getMacLength();
byte[] secure = new byte[cipher.getOutputSize(insecure.length)+ macLenght];
int secureCount = cipher.doFinal(insecure,0,insecure.length,secure);
mac.update(secure, 0, secureCount);
mac.doFinal(secure, secureCount);
return secure;
}
catch (Exception e)
{
throw new FacesException(e);
}
}
public static final byte[] compress(byte[] bytes)
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try
{
GZIPOutputStream gzip = new GZIPOutputStream(baos);
gzip.write(bytes, 0, bytes.length);
gzip.finish();
byte[] fewerBytes = baos.toByteArray();
gzip.close();
baos.close();
gzip = null;
baos = null;
return fewerBytes;
}
catch (IOException e)
{
throw new FacesException(e);
}
}
public static final byte[] encode(byte[] bytes)
{
return new Base64().encode(bytes);
}
/**
* This fires during the Restore View phase, restoring state.
*/
public static final Object reconstruct(String string, ExternalContext ctx)
{
byte[] bytes;
try
{
if(log.isDebugEnabled())
log.debug("Processing state : "+string);
bytes = string.getBytes(ZIP_CHARSET);
bytes = decode(bytes);
if(isSecure(ctx))
bytes = decrypt(bytes, ctx);
if( enableCompression(ctx) )
bytes = decompress(bytes);
return getAsObject(bytes, ctx);
}
catch (Throwable e)
{
if (log.isErrorEnabled())
{
log.error("View State cannot be reconstructed", e);
}
return null;
}
}
public static final byte[] decode(byte[] bytes)
{
return new Base64().decode(bytes);
}
public static final byte[] decompress(byte[] bytes)
{
if(bytes == null)
throw new NullPointerException("byte[] bytes");
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[bytes.length];
int length;
try
{
GZIPInputStream gis = new GZIPInputStream(bais);
while ((length = gis.read(buffer)) != -1)
{
baos.write(buffer, 0, length);
}
byte[] moreBytes = baos.toByteArray();
baos.close();
bais.close();
gis.close();
baos = null;
bais = null;
gis = null;
return moreBytes;
}
catch (IOException e)
{
throw new FacesException(e);
}
}
public static byte[] decrypt(byte[] secure, ExternalContext ctx)
{
if (ctx == null)
throw new NullPointerException("ExternalContext ctx");
testConfiguration(ctx);
SecretKey secretKey = (SecretKey) getSecret(ctx);
String algorithm = findAlgorithm(ctx);
String algorithmParams = findAlgorithmParams(ctx);
byte[] iv = findInitializationVector(ctx);
SecretKey macSecretKey = (SecretKey) getMacSecret(ctx);
String macAlgorithm = findMacAlgorithm(ctx);
try
{
// keep local to avoid threading issue
Mac mac = Mac.getInstance(macAlgorithm);
mac.init(macSecretKey);
Cipher cipher = Cipher.getInstance(algorithm + "/"
+ algorithmParams);
if (iv != null)
{
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
}
else
{
cipher.init(Cipher.DECRYPT_MODE, secretKey);
}
if (log.isDebugEnabled())
{
log.debug("decrypting w/ " + algorithm + "/" + algorithmParams);
}
//EtM Composition Approach
int macLenght = mac.getMacLength();
mac.update(secure, 0, secure.length-macLenght);
byte[] signedDigestHash = mac.doFinal();
boolean isMacEqual = true;
for (int i = 0; i < signedDigestHash.length; i++)
{
if (signedDigestHash[i] != secure[secure.length-macLenght+i])
{
isMacEqual = false;
// MYFACES-2934 Must compare *ALL* bytes of the hash,
// otherwise a side-channel timing attack is theorically possible
// but with a very very low probability, because the
// comparison time is too small to be measured compared to
// the overall request time and in real life applications,
// there are too many uncertainties involved.
//break;
}
}
if (!isMacEqual)
{
throw new ViewExpiredException();
}
return cipher.doFinal(secure, 0, secure.length-macLenght);
}
catch (Exception e)
{
throw new FacesException(e);
}
}
/**
* Performs deserialization with the serialization provider created from the
* SerialFactory.
*
* @param bytes
* @param ctx
* @return
*/
public static final Object getAsObject(byte[] bytes, ExternalContext ctx)
{
ByteArrayInputStream input = null;
try
{
input = new ByteArrayInputStream(bytes);
// get the Factory that was instantiated @ startup
SerialFactory serialFactory = (SerialFactory) ctx.getApplicationMap().get(SERIAL_FACTORY);
if(serialFactory == null)
throw new NullPointerException("serialFactory");
ObjectInputStream s = null;
Exception pendingException = null;
try
{
s = serialFactory.getObjectInputStream(input);
Object object = null;
if (System.getSecurityManager() != null)
{
final ObjectInputStream ois = s;
object = AccessController.doPrivileged(new PrivilegedExceptionAction