1 package org.apache.fulcrum.jce.crypto;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
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
129
130
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
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
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
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
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 }