View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
    10   *   http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing,
    13   * software distributed under the License is distributed on an
    14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   * KIND, either express or implied.  See the License for the
    16   * specific language governing permissions and limitations
    17   * under the License.
    18   */
    19  package org.apache.myfaces.shared_orchestra.util;
    20
    21  import java.io.ByteArrayInputStream;
    22  import java.io.ByteArrayOutputStream;
    23  import java.io.IOException;
    24  import java.io.ObjectInputStream;
    25  import java.io.ObjectOutputStream;
    26  import java.io.UnsupportedEncodingException;
    27  import java.security.AccessController;
    28  import java.security.NoSuchAlgorithmException;
    29  import java.security.PrivilegedActionException;
    30  import java.security.PrivilegedExceptionAction;
    31  import java.util.Random;
    32  import java.util.zip.GZIPInputStream;
    33  import java.util.zip.GZIPOutputStream;
    34
    35  import javax.crypto.Cipher;
    36  import javax.crypto.KeyGenerator;
    37  import javax.crypto.Mac;
    38  import javax.crypto.SecretKey;
    39  import javax.crypto.spec.IvParameterSpec;
    40  import javax.crypto.spec.SecretKeySpec;
    41  import javax.faces.FacesException;
    42  import javax.faces.context.ExternalContext;
    43  import javax.servlet.ServletContext;
    44
    45  import org.apache.commons.codec.binary.Base64;
    46  import org.apache.commons.logging.Log;
    47  import org.apache.commons.logging.LogFactory;
    48  import org.apache.myfaces.shared_orchestra.util.serial.SerialFactory;
    49
    50  /**
    51   * <p>This Class exposes a handful of methods related to encryption,
    52   * compression and serialization.</p>
    53   *
    54   * <ul>
    55   * <li>ISO-8859-1 is the character set used.</li>
    56   * <li>GZIP is used for all compression/decompression.</li>
    57   * <li>Base64 is used for all encoding and decoding.</li>
    58   * <li>DES is the default encryption algorithm</li>
    59   * <li>ECB is the default mode</li>
    60   * <li>PKCS5Padding is the default padding</li>
    61   * <li>HmacSHA1 is the default MAC algorithm</li>
    62   * <li>The default algorithm can be overridden using the
    63   * <i>org.apache.myfaces.ALGORITHM</i> parameter</li>
    64   * <li>The default mode and padding can be overridden using the
    65   * <i>org.apache.myfaces.ALGORITHM.PARAMETERS</i> parameter</li>
    66   * <li>This class has not been tested with modes other than ECB and CBC</li>
    67   * <li>An initialization vector can be specified via the
    68   * <i>org.apache.myfaces.ALGORITHM.IV</i> parameter</li>
    69   * <li>The default MAC algorithm can be overridden using the
    70   * <i>org.apache.myfaces.MAC_ALGORITHM</i> parameter</li>
    71   * </ul>
    72   *
    73   * <p>The secret is interpretted as base 64 encoded.  In other
    74   * words, if your secret is "76543210", you would put "NzY1NDMyMTA=" in
    75   * the deployment descriptor.  This is needed so that key values are not
    76   * limited to just values composed of printable characters.</p>
    77   *
    78   * <p>If you are using CBC mode encryption, you <b>must</b> specify an
    79   * initialization vector.</p>
    80   *
    81   * <p>If you are using the AES algorithm and getting a SecurityException
    82   * complaining about keysize, you most likely need to get the unlimited
    83   * strength jurisdiction policy files from a place like
    84   * http://java.sun.com/j2se/1.4.2/download.html .</p>
    85   *
    86   * @author Dennis C. Byrne
    87   * @see org.apache.myfaces.webapp.StartupServletContextListener
    88   */
    89  public final class StateUtils {
    90
    91      private static final Log log = LogFactory.getLog(StateUtils.class);
    92
    93      public static final String ZIP_CHARSET = "ISO-8859-1";
    94
    95      public static final String DEFAULT_ALGORITHM = "DES";
    96      public static final String DEFAULT_ALGORITHM_PARAMS = "ECB/PKCS5Padding";
    97
    98      public static final String INIT_PREFIX = "org.apache.myfaces.";
    99
    100     /**
    101      * Indicate if the view state is encrypted or not. By default, encryption is enabled.
    102      * @JSFWebConfigParam name="org.apache.myfaces.USE_ENCRYPTION" since="1.1.8"
    103      */
    104     public static final String USE_ENCRYPTION = INIT_PREFIX + "USE_ENCRYPTION";
    105
    106     /**
    107      * Defines the secret (Base64 encoded) used to initialize the secret key
    108      * for encryption algorithm. See MyFaces wiki/web site documentation
    109      * for instructions on how to configure an application for
    110      * different encryption strengths.
    111      * @JSFWebConfigParam name="org.apache.myfaces.SECRET" since="1.1"
    112      */
    113     public static final String INIT_SECRET = INIT_PREFIX + "SECRET";
    114
    115     /**
    116      * Indicate the encryption algorithm used for encrypt the view state.
    117      * @JSFWebConfigParam name="org.apache.myfaces.ALGORITHM" since="1.1"
    118      */
    119     public static final String INIT_ALGORITHM = INIT_PREFIX + "ALGORITHM";
    120
    121     /**
    122      * If is set to "false", the secret key used for encryption algorithm is not cached. This is used
    123      * when the returned SecretKey for encryption algorithm is not thread safe.
    124      * @JSFWebConfigParam name="org.apache.myfaces.SECRET.CACHE" since="1.1"
    125      **/
    126     public static final String INIT_SECRET_KEY_CACHE = INIT_SECRET + ".CACHE";
    127
    128     /**
    129      * Defines the initialization vector (Base64 encoded) used for the encryption algorithm
    130      * @JSFWebConfigParam name="org.apache.myfaces.ALGORITHM.IV" since="1.1"
    131      */
    132     public static final String INIT_ALGORITHM_IV = INIT_ALGORITHM + ".IV";
    133
    134     /**
    135      * Defines the default mode and padding used for the encryption algorithm
    136      * @JSFWebConfigParam name="org.apache.myfaces.ALGORITHM.PARAMETERS" since="1.1"
    137      */
    138     public static final String INIT_ALGORITHM_PARAM = INIT_ALGORITHM + ".PARAMETERS";
    139
    140     /**
    141      * Defines the factory class name using for serialize/deserialize the view state returned
    142      * by state manager into a byte array. The expected class must implement
    143      * org.apache.myfaces.shared_orchestra.util.serial.SerialFactory interface.
    144      * @JSFWebConfigParam name="org.apache.myfaces.SERIAL_FACTORY" since="1.1"
    145      */
    146     public static final String SERIAL_FACTORY = INIT_PREFIX + "SERIAL_FACTORY";
    147
    148     /**
    149      * Indicate if the view state should be compressed before encrypted(optional) and encoded
    150      * @JSFWebConfigParam name="org.apache.myfaces.COMPRESS_STATE_IN_CLIENT" since="1.1"
    151      */
    152     public static final String COMPRESS_STATE_IN_CLIENT = INIT_PREFIX + "COMPRESS_STATE_IN_CLIENT";
    153
    154     public static final String DEFAULT_MAC_ALGORITHM = "HmacSHA1";
    155
    156     /**
    157      * Indicate the algorithm used to calculate the Message Authentication Code that is
    158      * added to the view state.
    159      * @JSFWebConfigParam name="org.apache.myfaces.MAC_ALGORITHM"
    160      */
    161     public static final String INIT_MAC_ALGORITHM = "org.apache.myfaces.MAC_ALGORITHM";
    162
    163     /**
    164      * Define the initialization code that are used to initialize the secret key used
    165      * on the Message Authentication Code algorithm
    166      * @JSFWebConfigParam name="org.apache.myfaces.MAC_SECRET"
    167      */
    168     public static final String INIT_MAC_SECRET = "org.apache.myfaces.MAC_SECRET";
    169
    170     /**
    171      * If is set to "false", the secret key used for MAC algorithm is not cached. This is used
    172      * when the returned SecretKey for mac algorithm is not thread safe.
    173      * @JSFWebConfigParam name="org.apache.myfaces.MAC_SECRET.CACHE"
    174      */
    175     public static final String INIT_MAC_SECRET_KEY_CACHE = "org.apache.myfaces.MAC_SECRET.CACHE";
    176
    177     /** Utility class, do not instatiate */
    178     private StateUtils()
    179     {
    180         //nope
    181     }
    182
    183     private static void testConfiguration(ExternalContext ctx){
    184
    185         String algorithmParams = ctx.getInitParameter(INIT_ALGORITHM_PARAM);
    186
    187         if (algorithmParams == null)
    188         {
    189             algorithmParams = ctx.getInitParameter(INIT_ALGORITHM_PARAM.toLowerCase());
    190         }
    191         String iv = ctx.getInitParameter(INIT_ALGORITHM_IV);
    192
    193         if (iv == null)
    194         {
    195             iv = ctx.getInitParameter(INIT_ALGORITHM_IV.toLowerCase());
    196         }
    197
    198         if (algorithmParams != null && algorithmParams.startsWith("CBC") )
    199         {
    200             if(iv == null)
    201                 throw new FacesException(INIT_ALGORITHM_PARAM +
    202                                     " parameter has been set with CBC mode," +
    203                                     " but no initialization vector has been set " +
    204                                     " with " + INIT_ALGORITHM_IV);
    205         }
    206
    207     }
    208
    209     public static boolean enableCompression(ExternalContext ctx)
    210     {
    211         if(ctx == null)
    212             throw new NullPointerException("ExternalContext ctx");
    213
    214         return "true".equals(ctx.getInitParameter(COMPRESS_STATE_IN_CLIENT));
    215     }
    216
    217     public static boolean isSecure(ExternalContext ctx)
    218     {
    219
    220         if(ctx == null)
    221             throw new NullPointerException("ExternalContext ctx");
    222
    223         return ! "false".equals(ctx.getInitParameter(USE_ENCRYPTION));
    224     }
    225
    226     /**
    227      * This fires during the Render Response phase, saving state.
    228      */
    229
    230     public static final String construct(Object object, ExternalContext ctx){
    231         byte[] bytes = getAsByteArray(object, ctx);
    232         if( enableCompression(ctx) )
    233                 bytes = compress(bytes);
    234         if(isSecure(ctx))
    235                 bytes = encrypt(bytes, ctx);
    236         bytes = encode(bytes);
    237         try
    238         {
    239             return new String(bytes, ZIP_CHARSET);
    240         }
    241         catch (UnsupportedEncodingException e)
    242         {
    243             throw new FacesException(e);
    244         }
    245     }
    246
    247     /**
    248      * Performs serialization with the serialization provider created by the
    249      * SerialFactory.
    250      *
    251      * @param object
    252      * @param ctx
    253      * @return
    254      */
    255
    256     public static final byte[] getAsByteArray(Object object, ExternalContext ctx)
    257     {
    258         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    259
    260         // get the Factory that was instantiated @ startup
    261         SerialFactory serialFactory = (SerialFactory) ctx.getApplicationMap().get(SERIAL_FACTORY);
    262
    263         if(serialFactory == null)
    264             throw new NullPointerException("serialFactory");
    265
    266         try
    267         {
    268             ObjectOutputStream writer = serialFactory.getObjectOutputStream(outputStream);
    269             writer.writeObject(object);
    270             byte[] bytes = outputStream.toByteArray();
    271             writer.close();
    272             outputStream.close();
    273             writer = null;
    274             outputStream = null;
    275             return bytes;
    276         }
    277         catch (IOException e)
    278         {
    279             throw new FacesException(e);
    280         }
    281     }
    282
    283     public static byte[] encrypt(byte[] insecure, ExternalContext ctx)
    284     {
    285
    286         if (ctx == null)
    287             throw new NullPointerException("ExternalContext ctx");
    288
    289         testConfiguration(ctx);
    290
    291         SecretKey secretKey = (SecretKey) getSecret(ctx);
    292         String algorithm = findAlgorithm(ctx);
    293         String algorithmParams = findAlgorithmParams(ctx);
    294         byte[] iv = findInitializationVector(ctx);
    295
    296         SecretKey macSecretKey = (SecretKey) getMacSecret(ctx);
    297         String macAlgorithm = findMacAlgorithm(ctx);
    298
    299         try
    300         {
    301             // keep local to avoid threading issue
    302             Mac mac = Mac.getInstance(macAlgorithm);
    303             mac.init(macSecretKey);
    304             Cipher cipher = Cipher.getInstance(algorithm + "/" + algorithmParams);
    305             if (iv != null)
    306             {
    307                 IvParameterSpec ivSpec = new IvParameterSpec(iv);
    308                 cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
    309             }
    310             else
    311             {
    312                 cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    313             }
    314             if (log.isDebugEnabled())
    315             {
    316                 log.debug("encrypting w/ " + algorithm + "/" + algorithmParams);
    317             }
    318
    319             //EtM Composition Approach
    320             int macLenght = mac.getMacLength();
    321             byte[] secure = new byte[cipher.getOutputSize(insecure.length)+ macLenght];
    322             int secureCount = cipher.doFinal(insecure,0,insecure.length,secure);
    323             mac.update(secure, 0, secureCount);
    324             mac.doFinal(secure, secureCount);
    325
    326             return secure;
    327         }
    328         catch (Exception e)
    329         {
    330             throw new FacesException(e);
    331         }
    332     }
    333
    334     public static final byte[] compress(byte[] bytes)
    335     {
    336         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    337         try
    338         {
    339             GZIPOutputStream gzip = new GZIPOutputStream(baos);
    340             gzip.write(bytes, 0, bytes.length);
    341             gzip.finish();
    342             byte[] fewerBytes = baos.toByteArray();
    343             gzip.close();
    344             baos.close();
    345             gzip = null;
    346             baos = null;
    347             return fewerBytes;
    348         }
    349         catch (IOException e)
    350         {
    351             throw new FacesException(e);
    352         }
    353     }
    354
    355     public static final byte[] encode(byte[] bytes)
    356     {
    357           return new Base64().encode(bytes);
    358     }
    359
    360     /**
    361      * This fires during the Restore View phase, restoring state.
    362      */
    363     public static final Object reconstruct(String string, ExternalContext ctx)
    364     {
    365         byte[] bytes;
    366         try
    367         {
    368             if(log.isDebugEnabled())
    369                 log.debug("Processing state : "+string);
    370
    371             bytes = string.getBytes(ZIP_CHARSET);
    372             bytes = decode(bytes);
    373             if(isSecure(ctx))
    374                 bytes = decrypt(bytes, ctx);
    375             if( enableCompression(ctx) )
    376                 bytes = decompress(bytes);
    377             return getAsObject(bytes, ctx);
    378         }
    379         catch (Throwable e)
    380         {
    381             if (log.isErrorEnabled())
    382             {
    383                 log.error("View State cannot be reconstructed", e);
    384             }
    385             throw new ViewExpiredException();
    386         }
    387     }
    388
    389     public static final byte[] decode(byte[] bytes)
    390     {
    391           return new Base64().decode(bytes);
    392     }
    393
    394     public static final byte[] decompress(byte[] bytes)
    395     {
    396         if(bytes == null)
    397             throw new NullPointerException("byte[] bytes");
    398
    399         ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
    400         ByteArrayOutputStream baos = new ByteArrayOutputStream();
    401         byte[] buffer = new byte[bytes.length];
    402         int length;
    403
    404         try
    405         {
    406             GZIPInputStream gis = new GZIPInputStream(bais);
    407             while ((length = gis.read(buffer)) != -1)
    408             {
    409                 baos.write(buffer, 0, length);
    410             }
    411
    412             byte[] moreBytes = baos.toByteArray();
    413             baos.close();
    414             bais.close();
    415             gis.close();
    416             baos = null;
    417             bais = null;
    418             gis = null;
    419             return moreBytes;
    420         }
    421         catch (IOException e)
    422         {
    423             throw new FacesException(e);
    424         }
    425     }
    426
    427     public static byte[] decrypt(byte[] secure, ExternalContext ctx)
    428     {
    429         if (ctx == null)
    430             throw new NullPointerException("ExternalContext ctx");
    431
    432         testConfiguration(ctx);
    433
    434         SecretKey secretKey = (SecretKey) getSecret(ctx);
    435         String algorithm = findAlgorithm(ctx);
    436         String algorithmParams = findAlgorithmParams(ctx);
    437         byte[] iv = findInitializationVector(ctx);
    438
    439         SecretKey macSecretKey = (SecretKey) getMacSecret(ctx);
    440         String macAlgorithm = findMacAlgorithm(ctx);
    441
    442         try
    443         {
    444             // keep local to avoid threading issue
    445             Mac mac = Mac.getInstance(macAlgorithm);
    446             mac.init(macSecretKey);
    447             Cipher cipher = Cipher.getInstance(algorithm + "/"
    448                     + algorithmParams);
    449             if (iv != null)
    450             {
    451                 IvParameterSpec ivSpec = new IvParameterSpec(iv);
    452                 cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
    453             }
    454             else
    455             {
    456                 cipher.init(Cipher.DECRYPT_MODE, secretKey);
    457             }
    458             if (log.isDebugEnabled())
    459             {
    460                 log.debug("decrypting w/ " + algorithm + "/" + algorithmParams);
    461             }
    462
    463             //EtM Composition Approach
    464             int macLenght = mac.getMacLength();
    465             mac.update(secure, 0, secure.length-macLenght);
    466             byte[] signedDigestHash = mac.doFinal();
    467
    468             boolean isMacEqual = true;
    469             for (int i = 0; i < signedDigestHash.length; i++)
    470             {
    471                 if (signedDigestHash[i] != secure[secure.length-macLenght+i])
    472                 {
    473                     isMacEqual = false;
    474                     // MYFACES-2934 Must compare *ALL* bytes of the hash,
    475                     // otherwise a side-channel timing attack is theorically possible
    476                     // but with a very very low probability, because the
    477                     // comparison time is too small to be measured compared to
    478                     // the overall request time and in real life applications,
    479                     // there are too many uncertainties involved.
    480                     //break;
    481                 }
    482             }
    483             if (!isMacEqual)
    484             {
    485                 throw new FacesException("ViewExpiredException");
    486             }
    487
    488             return cipher.doFinal(secure, 0, secure.length-macLenght);
    489         }
    490         catch (Exception e)
    491         {
    492             throw new FacesException(e);
    493         }
    494     }
    495
    496     /**
    497      * Performs deserialization with the serialization provider created from the
    498      * SerialFactory.
    499      *
    500      * @param bytes
    501      * @param ctx
    502      * @return
    503      */
    504
    505     public static final Object getAsObject(byte[] bytes, ExternalContext ctx)
    506     {
    507         ByteArrayInputStream input = null;
    508
    509         try
    510         {
    511             input = new ByteArrayInputStream(bytes);
    512
    513             // get the Factory that was instantiated @ startup
    514             SerialFactory serialFactory = (SerialFactory) ctx.getApplicationMap().get(SERIAL_FACTORY);
    515
    516             if(serialFactory == null)
    517                 throw new NullPointerException("serialFactory");
    518
    519             ObjectInputStream s = null;
    520             Exception pendingException = null;
    521             try
    522             {
    523                 s = serialFactory.getObjectInputStream(input);
    524                 Object object = null;
    525                 if (System.getSecurityManager() != null)
    526                 {
    527                     final ObjectInputStream ois = s;
    528                     object = AccessController.doPrivileged(new PrivilegedExceptionAction()
    529                     {
    530                         //Put IOException and ClassNotFoundException as "checked" exceptions,
    531                         //so AccessController wrap them in a PrivilegedActionException
    532                         public Object run() throws  PrivilegedActionException, IOException, ClassNotFoundException
    533                         {
    534                             return ois.readObject();
    535                         }
    536                     });
    537                     // Since s has the same instance as ois,
    538                     // we don't need to close it here, rather
    539                     // close it on the finally block related to s
    540                     // and avoid duplicate close exceptions
    541                     // finally
    542                     // {
    543                     //    ois.close();
    544                     // }
    545                 }
    546                 else
    547                 {
    548                     object = s.readObject();
    549                 }
    550                 return object;
    551             }
    552             catch (Exception e)
    553             {
    554                 pendingException = e;
    555                 throw new FacesException(e);
    556             }
    557             finally
    558             {
    559                 if (s != null)
    560                 {
    561                     try
    562                     {
    563                         s.close();
    564                     }
    565                     catch (IOException e)
    566                     {
    567                         // If a previous exception is thrown
    568                         // ignore this, but if not, wrap it in a
    569                         // FacesException and throw it. In this way
    570                         // we preserve the original semantic of this
    571                         // method, but we handle correctly the case
    572                         // when we close a stream. Obviously, the
    573                         // information about this exception is lost,
    574                         // but note that the interesting information
    575                         // is always on pendingException, since we
    576                         // only do a readObject() on the outer try block.
    577                         if (pendingException == null)
    578                         {
    579                             throw new FacesException(e);
    580                         }
    581                     }
    582                     finally
    583                     {
    584                         s = null;
    585                     }
    586                 }
    587             }
    588         }
    589         finally
    590         {
    591             if (input != null)
    592             {
    593                 try
    594                 {
    595                     input.close();
    596                 }
    597                 catch (IOException e)
    598                 {
    599                     //ignore it, because ByteArrayInputStream.close has
    600                     //no effect, but it is better to call close and preserve
    601                     //semantic from previous code.
    602                 }
    603                 finally
    604                 {
    605                     input = null;
    606                 }
    607             }
    608         }
    609     }
    610
    611     /**
    612      * Utility method for generating base 64 encoded strings.
    613      *
    614      * @param args
    615      * @throws UnsupportedEncodingException
    616      */
    617     public static void main (String[] args) throws UnsupportedEncodingException
    618     {
    619         byte[] bytes = encode(args[0].getBytes(ZIP_CHARSET));
    620           System.out.println(new String(bytes, ZIP_CHARSET));
    621     }
    622
    623     private static byte[] findInitializationVector(ExternalContext ctx) {
    624
    625         byte[] iv = null;
    626         String _iv = ctx.getInitParameter(INIT_ALGORITHM_IV);
    627
    628         if(_iv == null)
    629         {
    630             _iv = ctx.getInitParameter(INIT_ALGORITHM_IV.toLowerCase());
    631         }
    632
    633         if (_iv != null)
    634             iv = new Base64().decode(_iv.getBytes());
    635
    636         return iv;
    637     }
    638
    639     private static String findAlgorithmParams(ExternalContext ctx) {
    640
    641         String algorithmParams = ctx.getInitParameter(INIT_ALGORITHM_PARAM);
    642
    643         if (algorithmParams == null)
    644         {
    645             algorithmParams = ctx.getInitParameter(INIT_ALGORITHM_PARAM.toLowerCase());
    646         }
    647
    648         if (algorithmParams == null)
    649         {
    650             algorithmParams = DEFAULT_ALGORITHM_PARAMS;
    651         }
    652
    653         if (log.isDebugEnabled())
    654         {
    655             log.debug("Using algorithm paramaters " + algorithmParams);
    656         }
    657
    658         return algorithmParams;
    659     }
    660
    661     private static String findAlgorithm(ExternalContext ctx) {
    662
    663         String algorithm = ctx.getInitParameter(INIT_ALGORITHM);
    664
    665         if (algorithm == null)
    666         {
    667             algorithm = ctx.getInitParameter(INIT_ALGORITHM.toLowerCase());
    668         }
    669
    670         return findAlgorithm( algorithm );
    671     }
    672
    673     private static String findAlgorithm(ServletContext ctx) {
    674
    675         String algorithm = ctx.getInitParameter(INIT_ALGORITHM);
    676
    677         if (algorithm == null)
    678         {
    679             algorithm = ctx.getInitParameter(INIT_ALGORITHM.toLowerCase());
    680         }
    681
    682         return findAlgorithm( algorithm );
    683     }
    684
    685     private static String findAlgorithm(String initParam) {
    686
    687         if (initParam == null)
    688         {
    689             initParam = DEFAULT_ALGORITHM;
    690         }
    691
    692         if (log.isDebugEnabled())
    693         {
    694             log.debug("Using algorithm " + initParam);
    695         }
    696
    697         return initParam;
    698
    699     }
    700
    701     /**
    702      * Does nothing if the user has disabled the SecretKey cache. This is
    703      * useful when dealing with a JCA provider whose SecretKey
    704      * implementation is not thread safe.
    705      *
    706      * Instantiates a SecretKey instance based upon what the user has
    707      * specified in the deployment descriptor.  The SecretKey is then
    708      * stored in application scope where it can be used for all requests.
    709      */
    710
    711     public static void initSecret(ServletContext ctx){
    712
    713         if(ctx == null)
    714             throw new NullPointerException("ServletContext ctx");
    715
    716         if (log.isDebugEnabled())
    717             log.debug("Storing SecretKey @ " + INIT_SECRET_KEY_CACHE);
    718
    719         // Create and store SecretKey on application scope
    720         String cache = ctx.getInitParameter(INIT_SECRET_KEY_CACHE);
    721
    722         if(cache == null)
    723         {
    724             cache = ctx.getInitParameter(INIT_SECRET_KEY_CACHE.toLowerCase());
    725         }
    726
    727         if (!"false".equals(cache))
    728         {
    729             String algorithm = findAlgorithm(ctx);
    730             // you want to create this as few times as possible
    731             ctx.setAttribute(INIT_SECRET_KEY_CACHE, new SecretKeySpec(findSecret(ctx, algorithm), algorithm));
    732         }
    733
    734         if (log.isDebugEnabled())
    735             log.debug("Storing SecretKey @ " + INIT_MAC_SECRET_KEY_CACHE);
    736
    737         String macCache = ctx.getInitParameter(INIT_MAC_SECRET_KEY_CACHE);
    738
    739         if(macCache == null)
    740         {
    741             macCache = ctx.getInitParameter(INIT_MAC_SECRET_KEY_CACHE.toLowerCase());
    742         }
    743
    744         if (!"false".equals(macCache))
    745         {
    746             String macAlgorithm = findMacAlgorithm(ctx);
    747             // init mac secret and algorithm
    748             ctx.setAttribute(INIT_MAC_SECRET_KEY_CACHE, new SecretKeySpec(findMacSecret(ctx, macAlgorithm), macAlgorithm));
    749         }
    750     }
    751
    752     private static SecretKey getSecret(ExternalContext ctx)
    753     {
    754         Object secretKey = (SecretKey) ctx.getApplicationMap().get(INIT_SECRET_KEY_CACHE);
    755
    756         if (secretKey == null)
    757         {
    758             String cache = ctx.getInitParameter(INIT_SECRET_KEY_CACHE);
    759
    760             if(cache == null)
    761             {
    762                 cache = ctx.getInitParameter(INIT_SECRET_KEY_CACHE.toLowerCase());
    763             }
    764
    765             if ("false".equals(cache))
    766             {
    767                 // No cache is used. This option is activated
    768                 String secret = ctx.getInitParameter(INIT_SECRET);
    769
    770                 if (secret == null)
    771                 {
    772                     secret = ctx.getInitParameter(INIT_SECRET.toLowerCase());
    773                 }
    774
    775                 if (secret == null)
    776                 {
    777                     throw new NullPointerException("Could not find secret using key '" + INIT_SECRET + "'");
    778                 }
    779
    780                 String algorithm = findAlgorithm(ctx);
    781
    782                 secretKey = new SecretKeySpec(findSecret(ctx, algorithm), algorithm);
    783             }
    784             else
    785             {
    786                 throw new NullPointerException("Could not find SecretKey in application scope using key '"
    787                         + INIT_SECRET_KEY_CACHE + "'");
    788             }
    789         }
    790
    791         if( ! ( secretKey instanceof SecretKey ) )
    792             throw new ClassCastException("Did not find an instance of SecretKey "
    793                     + "in application scope using the key '" + INIT_SECRET_KEY_CACHE + "'");
    794
    795
    796         return (SecretKey) secretKey;
    797     }
    798
    799     private static byte[] findSecret(ExternalContext ctx, String algorithm)
    800     {
    801         String secret = ctx.getInitParameter(INIT_SECRET);
    802
    803         if (secret == null)
    804         {
    805             secret = ctx.getInitParameter(INIT_SECRET.toLowerCase());
    806         }
    807
    808         return findSecret(secret, algorithm);
    809     }
    810
    811     private static byte[] findSecret(ServletContext ctx, String algorithm)
    812     {
    813         String secret = ctx.getInitParameter(INIT_SECRET);
    814
    815         if (secret == null)
    816         {
    817             secret = ctx.getInitParameter(INIT_SECRET.toLowerCase());
    818         }
    819
    820         return findSecret(secret, algorithm);
    821     }
    822
    823     private static byte[] findSecret(String secret, String algorithm) {
    824         byte[] bytes = null;
    825
    826         if(secret == null)
    827         {
    828             try
    829             {
    830                 KeyGenerator kg = KeyGenerator.getInstance(algorithm);
    831                 bytes = kg.generateKey().getEncoded();
    832
    833                 if(log.isDebugEnabled())
    834                     log.debug("generated random password of length " + bytes.length);
    835             }
    836             catch (NoSuchAlgorithmException e)
    837             {
    838                 // Generate random password length 8,
    839                 int length = 8;
    840                 bytes = new byte[length];
    841                 new Random().nextBytes(bytes);
    842
    843                 if(log.isDebugEnabled())
    844                     log.debug("generated random password of length " + length);
    845             }
    846         }
    847         else
    848         {
    849             bytes = new Base64().decode(secret.getBytes());
    850         }
    851
    852         return bytes;
    853     }
    854
    855     private static String findMacAlgorithm(ExternalContext ctx) {
    856
    857         String algorithm = ctx.getInitParameter(INIT_MAC_ALGORITHM);
    858
    859         if (algorithm == null)
    860         {
    861             algorithm = ctx.getInitParameter(INIT_MAC_ALGORITHM.toLowerCase());
    862         }
    863
    864         return findMacAlgorithm( algorithm );
    865
    866     }
    867
    868     private static String findMacAlgorithm(ServletContext ctx) {
    869
    870         String algorithm = ctx.getInitParameter(INIT_MAC_ALGORITHM);
    871
    872         if (algorithm == null)
    873         {
    874             algorithm = ctx.getInitParameter(INIT_MAC_ALGORITHM.toLowerCase());
    875         }
    876
    877         return findMacAlgorithm( algorithm );
    878
    879     }
    880
    881     private static String findMacAlgorithm(String initParam) {
    882
    883         if (initParam == null)
    884         {
    885             initParam = DEFAULT_MAC_ALGORITHM;
    886         }
    887
    888         if (log.isDebugEnabled())
    889         {
    890             log.debug("Using algorithm " + initParam);
    891         }
    892
    893         return initParam;
    894
    895     }
    896
    897     private static SecretKey getMacSecret(ExternalContext ctx)
    898     {
    899         Object secretKey = (SecretKey) ctx.getApplicationMap().get(INIT_MAC_SECRET_KEY_CACHE);
    900
    901         if (secretKey == null)
    902         {
    903             String cache = ctx.getInitParameter(INIT_MAC_SECRET_KEY_CACHE);
    904
    905             if(cache == null)
    906             {
    907                 cache = ctx.getInitParameter(INIT_MAC_SECRET_KEY_CACHE.toLowerCase());
    908             }
    909
    910             if ("false".equals(cache))
    911             {
    912                 // No cache is used. This option is activated
    913                 String secret = ctx.getInitParameter(INIT_MAC_SECRET);
    914
    915                 if (secret == null)
    916                 {
    917                     secret = ctx.getInitParameter(INIT_MAC_SECRET.toLowerCase());
    918                 }
    919
    920                 if (secret == null)
    921                 {
    922                     throw new NullPointerException("Could not find secret using key '" + INIT_MAC_SECRET + "'");
    923                 }
    924
    925                 String macAlgorithm = findMacAlgorithm(ctx);
    926
    927                 secretKey = new SecretKeySpec(findMacSecret(ctx, macAlgorithm), macAlgorithm);
    928             }
    929             else
    930             {
    931                 throw new NullPointerException("Could not find SecretKey in application scope using key '"
    932                         + INIT_MAC_SECRET_KEY_CACHE + "'");
    933             }
    934         }
    935
    936         if( ! ( secretKey instanceof SecretKey ) )
    937             throw new ClassCastException("Did not find an instance of SecretKey "
    938                     + "in application scope using the key '" + INIT_MAC_SECRET_KEY_CACHE + "'");
    939
    940
    941         return (SecretKey) secretKey;
    942     }
    943
    944     private static byte[] findMacSecret(ExternalContext ctx, String algorithm)
    945     {
    946         String secret = ctx.getInitParameter(INIT_MAC_SECRET);
    947
    948         if (secret == null)
    949         {
    950             secret = ctx.getInitParameter(INIT_MAC_SECRET.toLowerCase());
    951         }
    952
    953         return findMacSecret(secret, algorithm);
    954     }
    955
    956     private static byte[] findMacSecret(ServletContext ctx, String algorithm)
    957     {
    958         String secret = ctx.getInitParameter(INIT_MAC_SECRET);
    959
    960         if (secret == null)
    961         {
    962             secret = ctx.getInitParameter(INIT_MAC_SECRET.toLowerCase());
    963         }
    964
    965         return findMacSecret(secret, algorithm);
    966     }
    967
    968     private static byte[] findMacSecret(String secret, String algorithm) {
    969         byte[] bytes = null;
    970
    971         if(secret == null)
    972         {
    973             try
    974             {
    975                 KeyGenerator kg = KeyGenerator.getInstance(algorithm);
    976                 bytes = kg.generateKey().getEncoded();
    977
    978                 if(log.isDebugEnabled())
    979                     log.debug("generated random mac password of length " + bytes.length);
    980             }
    981             catch (NoSuchAlgorithmException e)
    982             {
    983                 // Generate random password length 8,
    984                 int length = 8;
    985                 bytes = new byte[length];
    986                 new Random().nextBytes(bytes);
    987
    988                 if(log.isDebugEnabled())
    989                     log.debug("generated random mac password of length " + length);
    990             }
    991         }
    992         else
    993         {
    994             bytes = new Base64().decode(secret.getBytes());
    995         }
    996
    997         return bytes;
    998     }
    999 }