View Javadoc

1   package org.apache.fulcrum.jce.crypto;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.security.GeneralSecurityException;
28  
29  
30  /**
31   * An input stream that determine if the originating input stream
32   * was encrypted or not. This magic only works for text files though.
33   *
34   * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
35   */
36  public class SmartDecryptingInputStream extends ByteArrayInputStream
37  {
38      /** The encodings to be checked for XML */
39      private static final  String[] ENCODINGS = { "ISO-8859-1", "UTF-8", "UTF-16" };
40  
41      /**
42       * Constructor
43       *
44       * @param cryptoStreamFactory the CryptoStreamFactory for creating a cipher stream
45       * @param is the input stream to be decrypted
46       */
47      public SmartDecryptingInputStream(
48          CryptoStreamFactory cryptoStreamFactory,
49          InputStream is )
50          throws IOException, GeneralSecurityException
51      {
52          this( cryptoStreamFactory, is, null );
53      }
54  
55      /**
56       * Constructor
57       *
58       * @param cryptoStreamFactory the CryptoStreamFactory for creating a cipher stream
59       * @param is the input stream to be decrypted
60       * @param password the password for decryption
61       */
62      public SmartDecryptingInputStream(
63          CryptoStreamFactory cryptoStreamFactory,
64          InputStream is,
65          char[] password )
66          throws IOException, GeneralSecurityException
67      {
68          super( new byte[0] );
69  
70          byte[] content = null;
71          byte[] plain = null;
72  
73          // store the data from the input stream
74  
75          ByteArrayOutputStream baosCipher = new ByteArrayOutputStream();
76          ByteArrayOutputStream baosPlain = new ByteArrayOutputStream();
77          this.copy( is, baosCipher );
78  
79          content = baosCipher.toByteArray();
80          plain = content;
81  
82          if( this.isEncrypted(content) == true )
83          {
84              InputStream cis = null;
85              ByteArrayInputStream bais = new ByteArrayInputStream(content);
86  
87              if( ( password != null ) && ( password.length > 0 ) )
88              {
89                  cis = cryptoStreamFactory.getInputStream( bais, password );
90              }
91              else
92              {
93                  cis = cryptoStreamFactory.getInputStream( bais );
94              }
95  
96              copy( cis, baosPlain );
97              plain = baosPlain.toByteArray();
98          }
99  
100         // initialize the inherited instance
101 
102         if( plain != null )
103         {
104             this.buf = plain;
105             this.pos = 0;
106             this.count = buf.length;
107         }
108     }
109 
110     /**
111      * Determine if the content is encrypted. We are
112      * using our knowledge about block lenght, check
113      * for XML, ZIP and PDF files and at the end of
114      * the day we are just guessing.
115      *
116      * @param content the data to be examined
117      * @return true if this is an encrypted file
118      */
119     private boolean isEncrypted( byte[] content )
120         throws IOException
121     {
122         if( content.length == 0 )
123         {
124             return false;
125         }
126         else if( ( content.length % 8 ) != 0 )
127         {
128             // the block length is 8 bytes - if the length
129             // is not a multipe of 8 then the content was
130             // definitely not encrypted
131             return false;
132         }
133         else if( this.isPDF(content) )
134         {
135             return false;
136         }
137         else if( this.isXML(content) )
138         {
139             return false;
140         }
141         else if( this.isZip(content) )
142         {
143             return false;
144         }
145         else if( this.isUtf16Text(content) )
146         {
147             return false;
148         }
149         else
150         {
151             for( int i=0; i<content.length; i++ )
152             {
153                 // do we have control characters in it?
154 
155                 char ch = (char) content[i];
156 
157                 if( this.isAsciiControl(ch) )
158                 {
159                     return true;
160                 }
161             }
162 
163             return false;
164         }
165     }
166 
167     /**
168      * Pumps the input stream to the output stream.
169      *
170      * @param is the source input stream
171      * @param os the target output stream
172      * @throws IOException the copying failed
173      */
174     public void copy( InputStream is, OutputStream os )
175         throws IOException
176     {
177         byte[] buf = new byte[1024];
178         int n = 0;
179         int total = 0;
180 
181         while ((n = is.read(buf)) > 0)
182         {
183             os.write(buf, 0, n);
184             total += n;
185         }
186 
187         is.close();
188 
189         os.flush();
190         os.close();
191     }
192 
193     /**
194      * Count the number of occurences for the given value
195      * @param content the content to examine
196      * @param value the value to look fo
197      * @return the number of matches
198      */
199     private int count( byte[] content, byte value )
200     {
201         int result = 0;
202 
203         for( int i=0; i<content.length; i++ )
204         {
205             if( content[i] == value )
206             {
207                 result++;
208             }
209         }
210 
211         return result;
212     }
213 
214     /**
215      * Detect the BOM of an UTF-16 (mandatory) or UTF-8 document (optional)
216      * @param content the content to examine
217      * @return true if the content contains a BOM
218      */
219     private boolean hasByteOrderMark( byte[] content )
220     {
221         if( ( (content[0] == 0xFF) && (content[1] == 0xFF) ) ||
222             ( (content[0] == 0xFF) && (content[1] == 0xFF) ) )
223         {
224             return true;
225         }
226         else
227         {
228             return false;
229         }
230     }
231 
232     /**
233      * Check this is a UTF-16 text document.
234      *
235      * @param content the content to examine
236      * @return true if it is a XML document
237      * @throws IOException
238      */
239     private boolean isUtf16Text( byte[] content ) throws IOException
240     {
241         if( content.length < 2 )
242         {
243             return false;
244         }
245 
246         if( this.hasByteOrderMark(content) == true )
247         {
248             // we should have plenty of 0x00 in a text file
249 
250             int estimate = (content.length-2)/3;
251 
252             if( this.count(content,(byte)0) > estimate )
253             {
254                 return true;
255             }
256         }
257 
258         return false;
259     }
260 
261     /**
262      * Check various encondings to determine if "<?xml"
263      * and "?>" appears in the data.
264      *
265      * @param content the content to examine
266      * @return true if it is a XML document
267      * @throws IOException
268      */
269     private boolean isXML( byte[] content ) throws IOException
270     {
271         if( content.length < 3 )
272         {
273             return false;
274         }
275 
276         for( int i=0; i<ENCODINGS.length; i++ )
277         {
278             String currEncoding = ENCODINGS[i];
279 
280             String temp = new String( content, currEncoding );
281 
282             if( ( temp.indexOf("<?xml") >= 0 ) && ( temp.indexOf("?>") > 0 ) )
283             {
284                 return true;
285             }
286         }
287 
288         return false;
289     }
290 
291     /**
292      * Check if this is a ZIP document
293      *
294      * @param content the content to examine
295      * @return true if it is a PDF document
296      * @throws IOException
297      */
298 
299     private boolean isZip( byte[] content )
300     {
301         if( content.length < 64 )
302         {
303             return false;
304         }
305         else
306         {
307             // A ZIP starts with Hex: "50 4B 03 04"
308 
309             if( ( content[0] == (byte) 0x50 ) &&
310                 ( content[1] == (byte) 0x4B ) &&
311                 ( content[2] == (byte) 0x03 ) &&
312                 ( content[3] == (byte) 0x04 )  )
313             {
314                 return true;
315             }
316             else
317             {
318                 return false;
319             }
320         }
321     }
322 
323     /**
324      * Check if this is a PDF document
325      *
326      * @param content the content to examine
327      * @return true if it is a PDF document
328      * @throws IOException
329      */
330     private boolean isPDF(byte[] content) throws IOException
331     {
332         if( content.length < 64 )
333         {
334             return false;
335         }
336         else
337         {
338             // A PDF starts with HEX "25 50 44 46 2D 31 2E"
339 
340             if( ( content[0] == (byte) 0x25 ) &&
341                 ( content[1] == (byte) 0x50 ) &&
342                 ( content[2] == (byte) 0x44 ) &&
343                 ( content[3] == (byte) 0x46 ) &&
344                 ( content[4] == (byte) 0x2D ) &&
345                 ( content[5] == (byte) 0x31 ) &&
346                 ( content[6] == (byte) 0x2E )  )
347             {
348                 return true;
349             }
350             else
351             {
352                 return false;
353             }
354         }
355     }
356 
357     /**
358      * Is this an ASCII control character
359      */
360     private boolean isAsciiControl(char ch)
361     {
362         if( ( ch >= 0x0000 ) && ( ch <= 0x001F) )
363         {
364             return true;
365         }
366         else
367         {
368             return true;
369         }
370     }
371 }