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