Java tutorial
/** * Copyright (C) 2009 GIP RECIA http://www.recia.fr * @Author (C) 2009 GIP RECIA <contact@recia.fr> * @Contributor (C) 2009 SOPRA http://www.sopragroup.com/ * @Contributor (C) 2011 Pierre Legay <pierre.legay@recia.fr> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * 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_impl.util; import org.apache.myfaces.shared_impl.util.serial.SerialFactory; import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import javax.faces.FacesException; import javax.faces.context.ExternalContext; import javax.servlet.ServletContext; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /** * <p>This Class exposes a handful of methods related to encryption, * compression and serialization.</p> * * <ul> * <li>ISO-8859-1 is the character set used.</li> * <li>GZIP is used for all compression/decompression.</li> * <li>Base64 is used for all encoding and decoding.</li> * </ul> * * <p>To enable encryption, a secret must be provided. StateUtils looks first * for the <i>org.apache.myfaces.SECRET</i> init param. * If a secret cannot be located, encryption is not used.</p> * * <ul> * <li>DES is the default encryption algorithm</li> * <li>ECB is the default mode</li> * <li>PKCS5Padding is the default padding</li> * <li>The default algorithm can be overridden using the * <i>org.apache.myfaces.ALGORITHM</i> parameter</li> * <li>The default mode and padding can be overridden using the * <i>org.apache.myfaces.algorithm.PARAMETERS</i> parameter</li> * <li>SecretKey cacheing can be disabled using the * <i>org.apache.myfaces.secret.CACHE</i> parameter</li> * <li>The defaults are not recommended</li> * <li>This utility has not been tested with modes other than ECB and CBC</li> * <li>An initialization vector can be specified via the * <i>org.apache.myfaces.algorithm.PARAMETERS</i> parameter</li> * </ul> * * <p>All parameters are interpretted as base 64 encoded keys. 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.</p> * * <p>If you are using CBC mode encryption, you <b>must</b> specify an * initialization vector. StateUtils will throw an exception otherwise.</p> * * <p>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 .</p> * * @author Dennis C. Byrne, ich * @see org.apache.myfaces.webapp.StartupServletContextListener * @noinspection UnusedAssignment */ 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."; public static final String INIT_SECRET = INIT_PREFIX + "SECRET"; public static final String INIT_ALGORITHM = INIT_PREFIX + "ALGORITHM"; public static final String INIT_SECRET_KEY_CACHE = INIT_PREFIX + "secret.CACHE"; public static final String INIT_ALGORITHM_IV = INIT_PREFIX + "algorithm.IV"; public static final String INIT_ALGORITHM_PARAM = INIT_PREFIX + "algorithm.PARAMETERS"; public static final String SERIAL_FACTORY = INIT_PREFIX + "SERIAL_FACTORY"; private static final String COMPRESS_STATE_IN_CLIENT = INIT_PREFIX + "COMPRESS_STATE_IN_CLIENT"; /** Utility class, do not instatiate */ private StateUtils() { //nope } private static void testConfiguration(String algorithmParams, String iv) { 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 ctx.getInitParameter(INIT_SECRET) != null || ctx.getInitParameter(INIT_SECRET.toLowerCase()) != null; } /** * This fires during the Render Response phase. */ public static 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); } } public static 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); //new ObjectOutputStream(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) { return symmetric(insecure, ctx, Cipher.ENCRYPT_MODE); } public static 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 byte[] encode(byte[] bytes) { return new Base64().encode(bytes); } /** * This fires during the Restore View phase. */ public static 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 th) { throw new FacesException("error while processing state : " + string, th); } } public static byte[] decode(byte[] bytes) { return new Base64().decode(bytes); } public static byte[] decompress(byte[] bytes) { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[2048]; 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) { return symmetric(secure, ctx, Cipher.DECRYPT_MODE); } public static Object getAsObject(byte[] bytes, ExternalContext ctx) { ByteArrayInputStream 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"); try { ObjectInputStream s = serialFactory.getObjectInputStream(input); //new MyFacesObjectInputStream(input); Object object = s.readObject(); s.close(); input.close(); s = null; input = null; return object; } catch (Exception e) { throw new FacesException(e); } } public static String encode64(Object obj) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream zos = new GZIPOutputStream(baos); ObjectOutputStream oos = new ObjectOutputStream(zos); oos.writeObject(obj); oos.close(); zos.close(); baos.close(); Base64 base64Codec = new Base64(); return new String(base64Codec.encode(baos.toByteArray()), ZIP_CHARSET); } catch (IOException e) { log.fatal("Cannot encode Object with Base64", e); throw new FacesException(e); } } public static void main(String[] args) throws UnsupportedEncodingException { byte[] bytes = encode(args[0].getBytes(ZIP_CHARSET)); System.out.println(new String(bytes, ZIP_CHARSET)); } private static byte[] symmetric(byte[] data, SecretKey secretKey, String algorithm, String algorithmParams, byte[] iv, int mode) { try { // keep local to avoid threading issue Cipher cipher = Cipher.getInstance(algorithm + "/" + algorithmParams); if (iv != null) { IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(mode, secretKey, ivSpec); } else { cipher.init(mode, secretKey); } if (log.isDebugEnabled()) { String action = mode == Cipher.ENCRYPT_MODE ? "encrypting" : "decrypting"; log.debug(action + " w/ " + algorithm + "/" + algorithmParams); } return cipher.doFinal(data); } catch (Exception e) { throw new FacesException(e); } } private static byte[] symmetric(byte[] data, byte[] secret, String algorithm, String algorithmParams, byte[] iv, int mode) { if (log.isDebugEnabled()) log.debug("creating new SecretKey"); SecretKey secretKey = new SecretKeySpec(secret, algorithm); return symmetric(data, secretKey, algorithm, algorithmParams, iv, mode); } private static byte[] symmetric(byte[] data, ExternalContext ctx, int mode) { if (ctx == null) throw new NullPointerException("ExternalContext ctx"); String _secret = ctx.getInitParameter(INIT_SECRET); if (_secret == null) { _secret = ctx.getInitParameter(INIT_SECRET.toLowerCase()); } String _algorithm = ctx.getInitParameter(INIT_ALGORITHM); if (_algorithm == null) { _algorithm = ctx.getInitParameter(INIT_ALGORITHM.toLowerCase()); } 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()); } String _cache = ctx.getInitParameter(INIT_SECRET_KEY_CACHE); if (_cache == null) { _cache = ctx.getInitParameter(INIT_SECRET_KEY_CACHE.toLowerCase()); } // use isSecure() before calling this method if (_secret == null) throw new NullPointerException("secret for " + INIT_SECRET + " not located in deployment descriptor"); if (_algorithm == null) { if (log.isDebugEnabled()) { log.debug("Using default algorithm " + DEFAULT_ALGORITHM); } _algorithm = DEFAULT_ALGORITHM; } if (_algorithmParams == null) { if (log.isDebugEnabled()) { log.debug("Using default algorithm paramaters " + DEFAULT_ALGORITHM_PARAMS); } _algorithmParams = DEFAULT_ALGORITHM_PARAMS; } testConfiguration(_algorithmParams, _iv); Base64 base64 = new Base64(); // TODO find a way to avoid decoding each time, maybe context listener byte[] iv = null; if (_iv != null) iv = base64.decode(_iv.getBytes()); if (_cache != null && "false".equals(_cache)) { // secret will have to be decoded and SecretKey will have to // be generated byte[] secret = base64.decode(_secret.getBytes()); return symmetric(data, secret, _algorithm, _algorithmParams, iv, mode); } else { Object object = ctx.getApplicationMap().get(INIT_SECRET_KEY_CACHE); if (object == null) { object = ctx.getApplicationMap().get(INIT_SECRET_KEY_CACHE.toLowerCase()); } if (object == null) throw new NullPointerException("The context parameter '" + INIT_SECRET_KEY_CACHE + "' is not set to false, " + "yet there is nothing stored in the application map " + "w/ the following key '" + INIT_SECRET_KEY_CACHE + "'. " + "It was either not placed there by StartupServletContextListener " + "or something has removed it."); if (!(object instanceof SecretKey)) throw new ClassCastException("Did not find an instance of SecretKey " + "in application scope using the key '" + INIT_SECRET_KEY_CACHE + "'"); if (log.isDebugEnabled()) log.debug("using cached SecretKey"); return symmetric(data, (SecretKey) object, _algorithm, _algorithmParams, iv, mode); } } /** * Does nothing if the user has disabled the SecretKey cache. This is * useful when dealing with a JCA provider whose SecretKey * implementation is not thread safe. * * Instantiates a SecretKey instance based upon what the user has * specified in the deployment descriptor. The SecretKey is then * stored in application scope where it can be used for all requests. */ public static void initSecret(ServletContext ctx) { if (ctx == null) throw new NullPointerException("ServletContext ctx"); String cache = ctx.getInitParameter(INIT_SECRET_KEY_CACHE); if (cache == null) { cache = ctx.getInitParameter(INIT_SECRET_KEY_CACHE.toLowerCase()); } if ("false".equals(cache)) return; String _secret = ctx.getInitParameter(INIT_SECRET); if (_secret == null) { _secret = ctx.getInitParameter(INIT_SECRET.toLowerCase()); } String _algorithm = ctx.getInitParameter(INIT_ALGORITHM); if (_algorithm == null) { _algorithm = ctx.getInitParameter(INIT_ALGORITHM.toLowerCase()); } if (_algorithm == null) { if (log.isDebugEnabled()) { log.debug("Using default algorithm " + DEFAULT_ALGORITHM); } _algorithm = DEFAULT_ALGORITHM; } if (_secret == null) throw new NullPointerException("_secret String - '" + INIT_SECRET_KEY_CACHE + "' has been enabled, " + "but there is no '" + INIT_SECRET + "'"); byte[] secret = new Base64().decode(_secret.getBytes()); // you want to do this as few times as possible SecretKey secretKey = new SecretKeySpec(secret, _algorithm); if (log.isDebugEnabled()) log.debug("Storing SecretKey @ " + INIT_SECRET_KEY_CACHE); ctx.setAttribute(INIT_SECRET_KEY_CACHE, secretKey); } }