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