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