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 }