View Javadoc

1   
2   package es.caib.signatura.impl;
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.io.UnsupportedEncodingException;
6   
7   import es.caib.signatura.api.Signature;
8   
9   /** 
10   * Clase que transforma un InputStream cualquiera en un InputStream en formato SMIME, estableciendo
11   * en la cabecera el content-type que se le pasa como argumento al constructor y codificando los 
12   * bytes de entrada en base64.
13   * 
14   * Modificacion introducidas por FGU para permitir generacion del SMIME a partir de un PKCS#7 que contiene una firma
15   * en paralelo:
16   * 
17   *  * Se intoruduce nuevo constructor a partir de un array de bytes que contiene el pkcs#7 y el contentType
18   *  * Se introduce el atributo contentType para en el metodo fetchContentHeader no tener que obtenerlo de la signature
19   *  * El constructor que utiliza la Signature da valor al atribute contentType
20   *  
21   * @author  Biel Buades
22   * @version 1.0
23   */
24  public class SMIMEInputStream extends InputStream {
25  
26  	private static final int CHUNK_SIZE = 48;
27  	String boundary;
28  	Signature signature;
29  	InputStream content;
30    String contentType;
31    
32    
33    
34      /** Flag que indica si se han leido todos los bytes de la entrada */
35  	private int status;
36  	static private final int STATUS_IDLE = 0;
37  	static private final int STATUS_HEADER = 1;
38  	static private final int STATUS_CONTENT_HEADER = 2;
39  	static private final int STATUS_CONTENT_BODY = 3;
40  	static private final int STATUS_SIGNATURE_HEADER = 4;
41  	static private final int STATUS_SIGNATURE_BODY = 5;
42  	static private final int STATUS_FOOTER = 6;
43  	static private final int STATUS_END = 7;
44  	
45  	//PJR 23/10/2008 hemos de calcular si el tamaño total del stream es múltiplo del CHUNK_SIZE para que no poner un salto de línea que genera un formato SMIME incorrecto. 
46  	private boolean contentSizeMultiple=false;
47  	private boolean signatureSizeMultiple=false;
48  	
49  	int bufferOffset;
50  	byte buffer [];
51  
52  	byte signatureData [];
53  	int signatureDataOffset ;
54  // Constructor
55  
56      /** Crea un nuevo MIMEInputStream 
57       * @throws UnsupportedEncodingException
58       *
59       * @param signature Firma electrónica válida para el documento a encapsular
60       * @param content InputStream con el contenido del documento firmado. 
61       * 
62       */
63      public SMIMEInputStream(Signature signature, InputStream content)  throws UnsupportedEncodingException{
64      	this.signature = signature;
65      	this.content = content;
66        this.contentType = signature.getContentType();
67      	status = STATUS_IDLE;
68      	bufferOffset = 0;
69      	buffer = null;
70      	boundary = "----CAIB_BOUNDARY_"+System.currentTimeMillis()+"_DGTIC";
71      	signatureData = signature.getPkcs7();
72      	signatureDataOffset = 0;
73      }
74      
75      /**
76     * Crea un nuevo SMIMEInputStream a partir de la firma en pkcs7 y el contentType del MIME
77     * @param pkcs7 el array de bytes que representa el pkcs7
78     * @param content El contenido que se firma.
79     * @param contentType El tipo del contenido que se firma.
80     */
81      public SMIMEInputStream(byte[] pkcs7, InputStream content, String contentType) {
82        this.content = content;
83        this.contentType = contentType;
84        status = STATUS_IDLE;
85        bufferOffset = 0;
86        buffer = null;
87        boundary = "----CAIB_BOUNDARY_"+System.currentTimeMillis()+"_DGTIC";
88        signatureData = pkcs7;
89        signatureDataOffset = 0;
90        
91      }
92        
93     
94      
95      /* (non-Javadoc)
96       * @see java.io.InputStream#close()
97       */
98      public synchronized void close ()
99      throws IOException {
100         status = STATUS_END;
101         content.close ();
102     }
103 
104     /* (non-Javadoc)
105      * @see java.io.InputStream#read()
106      */
107     public synchronized int read ()
108     throws IOException {
109     	do {
110     		if ( buffer != null && bufferOffset < buffer.length)
111 	    	{
112 	    		byte b = buffer [ bufferOffset ++ ];
113 	    		return b;
114 	    	} else if (status == STATUS_END) {
115     			return -1;
116 	    	} else {
117 	    		fetchData();
118 	    	}
119 	    	
120     	} while (true);
121     }
122 
123     /* (non-Javadoc)
124      * @see java.io.InputStream#read(byte[], int, int)
125      */
126     public synchronized int read (byte[] targetBuffer, int offset, int length)
127     throws IOException {
128     	do {
129     		if ( buffer != null && bufferOffset < buffer.length)
130 	    	{
131     			int actualLength = buffer.length - bufferOffset;
132     			if ( actualLength > length)
133     				actualLength = length;
134     			System.arraycopy(buffer, bufferOffset, targetBuffer, offset, actualLength);
135     			bufferOffset = bufferOffset + actualLength;
136     			return actualLength;
137 	    	} else if (status == STATUS_END) {
138         		return -1;
139 	    	} else {
140 	    		fetchData();
141 	    	}
142 	    	
143     	} while (true);
144     }
145 
146     /* (non-Javadoc)
147      * @see java.io.InputStream#available()
148      */
149     public synchronized int available()
150     throws IOException {
151     	do {
152     		 if ( buffer != null && bufferOffset < buffer.length)
153 	    	{
154     			return buffer.length - bufferOffset;
155 	    	} else if (status == STATUS_END) {
156 	    		return -1;
157 	    	} else if (status == STATUS_CONTENT_BODY) {
158 	    		return content.available();
159 	    	} else {
160 	    		fetchData ();
161 	    	}
162     	} while (true);
163     }
164 
165 	private void fetchData() throws IOException {
166 		switch (status)
167 		{
168 		case STATUS_IDLE:
169 			buffer = fetchIdle ();
170 			break;
171 		case STATUS_HEADER:
172 			buffer = fetchHeader ();
173 			break;
174 		case STATUS_CONTENT_HEADER:
175 			buffer = fetchContentHeader ();
176 			break;
177 		case STATUS_CONTENT_BODY:
178 			buffer = fetchContentBody ();
179 			break;
180 		case STATUS_SIGNATURE_HEADER:
181 			buffer = fetchSignatureHeader ();
182 			break;
183 		case STATUS_SIGNATURE_BODY:
184 			buffer = fetchSignatureBody ();
185 			break;
186 		case STATUS_FOOTER:
187 			buffer = fetchFooter ();
188 			break;
189 		default:
190 			buffer = null;
191 			break;
192 		}
193 		bufferOffset = 0;
194 	}
195 
196 
197 
198 
199     private byte [] fetchFooter() {
200     	try {
201         	status = STATUS_END;
202         	
203         	String outString=(signatureSizeMultiple)?"":"\r\n"; //si el tamaño es divisible entre CHUNK_SIZE no ponemos salto de línea
204 			outString+="--"+boundary+"--\r\n";
205 			
206 			return outString.getBytes("ISO-8859-1");
207 		} catch (UnsupportedEncodingException e) {
208 			throw new RuntimeException (e);
209 		}
210 	}
211 
212 
213 	private byte [] fetchContentBody() throws IOException {
214 		byte b[] = new byte[CHUNK_SIZE];
215 		int offset = 0;
216 		int read;
217 		contentSizeMultiple=false;
218 		
219 		while (true)
220 		{
221 			read = content.read(b, offset, b.length-offset);
222 			if ( read <= 0)
223 			{
224 				//si el tamaño final es divisible entre CHUNK_SIZE no ponemos salto de línea
225 				if((offset % CHUNK_SIZE)==0) 
226 					contentSizeMultiple=true;
227 				
228 				status = STATUS_SIGNATURE_HEADER;
229 				break;
230 			}
231 			offset = offset + read;
232 			if ( offset == b.length)
233 				break;
234 		}
235 		return Base64.toBase64(b, 0, offset);
236 	}
237 
238 
239 	private byte [] fetchContentHeader() {
240     	try {
241         	status = STATUS_CONTENT_BODY;
242 			return ("--"+boundary+"\r\n"+
243 					"Content-Type: "+contentType+"\r\n"+
244 					"Content-Transfer-Encoding: base64\r\n" +          
245 					"\r\n").getBytes("ISO-8859-1");
246 		} catch (UnsupportedEncodingException e) {
247 			throw new RuntimeException (e);
248 		}
249 	}
250 
251 
252 
253 	private byte [] fetchSignatureBody() {
254 		int len = signatureData.length - signatureDataOffset;
255 		signatureSizeMultiple=false;
256 		
257 		if ( len <= 0)
258 		{
259 			//si el tamaño final es divisible entre CHUNK_SIZE no ponemos salto de línea
260 			if((signatureDataOffset % CHUNK_SIZE)==0)
261 				signatureSizeMultiple=true;
262 			
263 			status = STATUS_FOOTER;
264 			return null;
265 		}
266 		else if ( len > CHUNK_SIZE )
267 		{
268 			len = CHUNK_SIZE;
269 		}
270 		
271 		byte result []= Base64.toBase64(signatureData, signatureDataOffset, len);
272 		signatureDataOffset = signatureDataOffset + len;
273 		return result;
274 	}
275 
276 
277 	private byte [] fetchSignatureHeader() {
278     	try {
279         	status = STATUS_SIGNATURE_BODY;
280 			
281         	String outString=(contentSizeMultiple)?"":"\r\n";	//si el tamaño es divisible entre CHUNK_SIZE no ponemos salto de línea
282 			outString+="--"+boundary+"\r\n"+
283 					"Content-Type: application/pkcs7-signature; name=smime.p7s; smime-type=signed-data\r\n"+
284 					"Content-Transfer-Encoding: base64\r\n"+
285 					"Content-Disposition: attachment; filename=\"smime.p7s\"\r\n"+
286 					"Content-Description: S/MIME Cryptographic Signature\r\n"+
287 					"\r\n";
288         	return outString.getBytes("ISO-8859-1");
289 		} catch (UnsupportedEncodingException e) {
290 			throw new RuntimeException (e);
291 		}
292 	}
293 
294 
295 	private byte [] fetchHeader() {
296     	try {
297         	status = STATUS_CONTENT_HEADER;
298 			return ("Content-Type: multipart/signed; " +
299 					"protocol=\"application/pkcs7-signature\"; " +
300 					"micalg=\"sha1\";" +
301 					"boundary=\""+boundary+"\"\r\n"+
302 					"\r\n").getBytes("ISO-8859-1");
303 		} catch (UnsupportedEncodingException e) {
304 			throw new RuntimeException (e);
305 		}
306 	}
307 
308 
309 	private byte [] fetchIdle() {
310 		status = STATUS_HEADER;
311 		return null;
312 	}
313 
314 
315 	
316 
317   
318     /* (non-Javadoc)
319      * @see java.io.InputStream#skip(long)
320      */
321     public synchronized long skip(long n) throws IOException {
322         for (long i = 0; i < n; i++)
323             if (this.read() < 0) return i;
324         return n;
325     }
326 
327     /* (non-Javadoc)
328      * @see java.io.InputStream#mark(int)
329      */
330     public void mark(int readlimit) {}
331 
332     /* (non-Javadoc)
333      * @see java.io.InputStream#reset()
334      */
335     public void reset() throws IOException {
336         throw new IOException("Base64InputStream does not support mark/reset");
337     }
338 
339     /* (non-Javadoc)
340      * @see java.io.InputStream#markSupported()
341      */
342     public boolean markSupported() { return false; }
343 
344 
345 // Own methods
346     
347     
348 }