View Javadoc

1   /**
2    * LICENCIA LGPL:
3    * 
4    * Esta librería es Software Libre; Usted puede redistribuirla y/o modificarla
5    * bajo los términos de la GNU Lesser General Public License (LGPL) tal y como 
6    * ha sido publicada por la Free Software Foundation; o bien la versión 2.1 de 
7    * la Licencia, o (a su elección) cualquier versión posterior.
8    * 
9    * Esta librería se distribuye con la esperanza de que sea útil, pero SIN 
10   * NINGUNA GARANTÍA; tampoco las implícitas garantías de MERCANTILIDAD o 
11   * ADECUACIÓN A UN PROPÓSITO PARTICULAR. Consulte la GNU Lesser General Public 
12   * License (LGPL) para más detalles
13   * 
14   * Usted debe recibir una copia de la GNU Lesser General Public License (LGPL) 
15   * junto con esta librería; si no es así, escriba a la Free Software Foundation 
16   * Inc. 51 Franklin Street, 5º Piso, Boston, MA 02110-1301, USA o consulte
17   * <http://www.gnu.org/licenses/>.
18   *
19   * Copyright 2011 Agencia de Tecnología y Certificación Electrónica
20   */
21  package es.accv.arangi.base.signature;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  import java.security.cert.CertificateEncodingException;
32  import java.util.ArrayList;
33  import java.util.HashMap;
34  import java.util.Iterator;
35  import java.util.List;
36  
37  import org.apache.log4j.Logger;
38  import org.bouncycastle.asn1.x509.KeyPurposeId;
39  import org.bouncycastle.cert.ocsp.BasicOCSPResp;
40  
41  import com.itextpdf.text.pdf.AcroFields;
42  import com.itextpdf.text.pdf.PdfName;
43  import com.itextpdf.text.pdf.PdfPKCS7;
44  import com.itextpdf.text.pdf.PdfReader;
45  
46  import es.accv.arangi.base.certificate.Certificate;
47  import es.accv.arangi.base.certificate.validation.CertificateOCSPResponse;
48  import es.accv.arangi.base.certificate.validation.OCSPResponse;
49  import es.accv.arangi.base.certificate.validation.ValidateCertificate;
50  import es.accv.arangi.base.document.IDocument;
51  import es.accv.arangi.base.exception.CertificateException;
52  import es.accv.arangi.base.exception.certificate.NormalizeCertificateException;
53  import es.accv.arangi.base.exception.document.HashingException;
54  import es.accv.arangi.base.exception.signature.SignatureException;
55  import es.accv.arangi.base.exception.timestamp.MalformedTimeStampException;
56  import es.accv.arangi.base.timestamp.TimeStamp;
57  import es.accv.arangi.base.util.Util;
58  import es.accv.arangi.base.util.validation.ValidationResult;
59  
60  /**
61   * @author <a href="mailto:jgutierrez@accv.es">José M Gutiérrez</a>
62   *
63   */
64  public abstract class BasePDFSignature extends Signature {
65  
66  	static Logger logger = Logger.getLogger(BasePDFSignature.class);
67  	
68  	/*
69  	 * Constante con el texto de "firmado por" en firmas visibles 
70  	 */
71  	protected static final String DIGITALLY_SIGNED_TEXT = "Firmado digitalmente por";
72  
73  	/*
74  	 * Constante con el texto de "estado" en firmas visibles 
75  	 */
76  	protected static final String STATUS_TEXT = "Estado";
77  
78  	/**
79  	 * Objeto PDF
80  	 */
81  	protected File pdfFile;
82  	
83  	//-- Métodos implementados de Signature
84  	
85  	/* (non-Javadoc)
86  	 * @see es.accv.arangi.base.signature.ISignature#getCertificates()
87  	 */
88  	public Certificate[] getCertificates() {
89  		logger.debug("[PDFSignature.getCertificates]::Entrada");
90  		
91  		PdfReader reader;
92  		try {
93  			reader = new PdfReader(this.pdfFile.getAbsolutePath());
94  		} catch (IOException e) {
95  			// El fichero ya pasó la validación
96  			logger.info("[PDFSignature.getCertificates]::No se puede leer el contenido de este objeto", e);
97  			return null;
98  		}
99  		AcroFields af = reader.getAcroFields();
100 		List<String> names = getRealSignatureNames(af);
101 		List<Certificate> result = new ArrayList<Certificate>(); 
102 		for (int i = 0; i < names.size(); i++) {
103 			String name = (String)names.get(i);
104 			
105 			//-- Validar que el PKCS#7 se corresponde con el documento
106 			PdfPKCS7 pkcs7 = af.verifySignature(name, CRYPTOGRAPHIC_PROVIDER_NAME);
107 			try {
108 				Certificate signingCertificate = getSigningCertificate(pkcs7.getCertificates());
109 				if (signingCertificate != null) {
110 					result.add(getSigningCertificate (pkcs7.getCertificates()));
111 				}
112 			} catch (NormalizeCertificateException e) {
113 				logger.info("[PDFSignature.getCertificates]::No se puede normalizar el certificado::" + pkcs7.getCertificates()[0], e);
114 			} 
115 		}
116 		
117 		reader.close();
118 		
119 		return result.toArray(new Certificate[0]);
120 	}
121 
122 	/* (non-Javadoc)
123 	 * @see es.accv.arangi.base.signature.ISignature#isValidSignatureOnly()
124 	 */
125 	public ValidationResult[] isValidSignatureOnly() throws SignatureException {
126 		
127 		logger.debug("[BasePDFSignature.isValidSignatureOnly]::Entrada");
128 		
129 		PdfReader reader;
130 		try {
131 			reader = new PdfReader(this.pdfFile.getAbsolutePath());
132 		} catch (IOException e) {
133 			// El fichero ya pasó la validación
134 			logger.info("[BasePDFSignature.isValidSignatureOnly]::No se puede leer el contenido de este objeto", e);
135 			return null;
136 		}
137 		AcroFields af = reader.getAcroFields();
138 		ArrayList names = getRealSignatureNames(af);
139 		ValidationResult[] result = new ValidationResult [names.size()]; 
140 		for (int i = 0; i < names.size(); i++) {
141 			String name = (String)names.get(i);
142 			PdfPKCS7 pkcs7 = af.verifySignature(name, CRYPTOGRAPHIC_PROVIDER_NAME);
143 			try {
144 				if (pkcs7.verify()) {
145 					result [i] = new ValidationResult (pkcs7.getSigningCertificate());
146 				} else {
147 					result [i] = new ValidationResult (ValidationResult.RESULT_SIGNATURE_NOT_MATCH_DATA, pkcs7.getSigningCertificate());
148 				}
149 			} catch (java.security.SignatureException e) {
150 				logger.info("[BasePDFSignature.isValidSignatureOnly]::Error en una de las firmas del PDF", e);
151 				throw new SignatureException ("Error en una de las firmas del PDF", e);
152 			}
153 		}
154 		
155 		return result;
156 	}
157 
158 	/**
159 	 * En el caso de los PDF no tiene sentido realizar la validación sobre un 
160 	 * documento que no sea el mismo PDF. Por ello, el resultado de este método 
161 	 * será igual a llamar a {@link #isValidSignatureOnly() isValidSignatureOnly} 
162 	 * sin parámetros. 
163 	 */
164 	public ValidationResult[] isValidSignatureOnly(IDocument document)
165 			throws HashingException, SignatureException {
166 		return isValidSignatureOnly();
167 	}
168 	
169 	/**
170 	 * Guarda la firma en disco
171 	 * 
172 	 * @param file Fichero donde se guardará la firma
173 	 * @throws IOException Errores de entrada / salida
174 	 */
175 	public void save (File file) throws IOException {
176 		logger.debug ("[PDFSignature.save]::Entrada::" + file);
177 		
178 		FileInputStream fis = null;
179 		try {
180 			fis = new FileInputStream(this.pdfFile);
181 			Util.saveFile(file, fis);
182 		} finally {
183 			if (fis != null) {
184 				fis.close();
185 			}
186 		}
187 	}
188 	
189 	/**
190 	 * Guarda la firma en un stream de escritura.
191 	 * 
192 	 * @param out Stream de escritura
193 	 * @throws IOException Errores de entrada / salida
194 	 */
195 	public void save (OutputStream out) throws IOException {
196 		logger.debug ("[PDFSignature.save]::Entrada::" + out);
197 		
198 		FileInputStream fis = null;
199 		try {
200 			fis = new FileInputStream(this.pdfFile);
201 			Util.save(out, fis);
202 		} finally {
203 			if (fis != null) {
204 				fis.close();
205 			}
206 		}
207 	}
208 	
209 	/**
210 	 * Devuelve el PDF en forma de array de bytes
211 	 * 
212 	 * @return Array de bytes con el contenido del PDF
213 	 * @throws IOException 
214 	 */
215 	public byte[] toByteArray () throws IOException {
216 		return Util.loadFile(this.pdfFile);
217 	}
218 	
219 	/**
220 	 * Método que obtiene un objeto de firma PDF o PADES-LTV en base
221 	 * a la firma que se pasa como parámetro. 
222 	 * 
223 	 * @param signature Firma PDF o PADES-LTV
224 	 * @return Objeto de firma PDF adecuado a la firma pasada como parámetro
225 	 * @throws SignatureException La firma no parece ser un PDF
226 	 */
227 	public static BasePDFSignature getPDFObject (byte[] signature) throws SignatureException {
228 		return getPDFObject(new ByteArrayInputStream(signature));
229 	}
230 	
231 	/**
232 	 * Método que obtiene un objeto de firma PDF o PADES-LTV en base
233 	 * a la firma que se pasa como parámetro. 
234 	 * 
235 	 * @param signature Firma PDF o PADES-LTV
236 	 * @return Objeto de firma PDF adecuado a la firma pasada como parámetro
237 	 * @throws SignatureException La firma no parece ser un PDF
238 	 * @throws FileNotFoundException El fichero no existe
239 	 */
240 	public static BasePDFSignature getPDFObject (File signature) throws SignatureException, FileNotFoundException {
241 		return getPDFObject(new FileInputStream(signature));
242 	}
243 	
244 	/**
245 	 * Método que obtiene un objeto de firma PDF o PADES-LTV en base
246 	 * a la firma que se pasa como parámetro. 
247 	 * 
248 	 * @param signature Firma PDF o PADES-LTV
249 	 * @return Objeto de firma PDF adecuado a la firma pasada como parámetro
250 	 * @throws SignatureException La firma no parece ser un PDF
251 	 */
252 	public static BasePDFSignature getPDFObject (InputStream signature) throws SignatureException {
253 		logger.debug("[BasePDFSignature.getPDFObject]::Entrada::" + signature);
254 		
255 		//-- Guardar en un fichero temporal
256 		FileOutputStream fos = null;
257 		File fileTemp;
258 		try {
259 			fileTemp = getFileTemp ();
260             Util.saveFile(fileTemp, signature);
261 		} catch (IOException e) {
262 			logger.info("[PAdESLTVSignature(byte[])::No se puede crear el fichero temporal o no se puede escribir en él", e);
263 			throw new SignatureException ("No se puede crear el fichero temporal o no se puede escribir en él", e);
264 		} finally {
265 			if (fos != null) {
266 				try {
267 					fos.close();
268 				} catch (IOException e) {
269 					logger.info("[PAdESLTVSignature(byte[])::No se puede cerrar el stream de lectura al fichero temporal", e);
270 					throw new SignatureException ("No se puede cerrar el stream de lectura al fichero temporal", e);
271 				}
272 			}
273 		}
274 		
275 		PdfReader reader;
276 		try {
277 			reader = new PdfReader(fileTemp.getAbsolutePath());
278 		} catch (IOException e) {
279 			// El fichero ya pasó la validación
280 			logger.info("[BasePDFSignature.getPDFObject]::La firma no parece ser un PDF", e);
281 			throw new SignatureException("La firma no parece ser un PDF", e);
282 		}
283 		
284 		try {
285 			AcroFields af = reader.getAcroFields();
286 			ArrayList names = af.getSignatureNames();
287 			if (names == null || names.isEmpty()) {
288 				logger.info("[BasePDFSignature.getPDFObject]::El documento PDF no está firmado");
289 				throw new SignatureException ("El documento PDF no está firmado");
290 			}
291 			if (names.size() == 1) {
292 				logger.debug("[BasePDFSignature.getPDFObject]::El documento PDF no es PAdES-LTV: sólo hay una firma y debería haber firma y sello de tiempos");
293 				return new PDFSignature(fileTemp);
294 			}
295 			
296 			//-- Comprobar que existe el diccionario DSS
297 			if (reader.getCatalog().get(new PdfName("DSS")) == null) {
298 				logger.debug("[BasePDFSignature.getPDFObject]::El documento PDF no es PAdES-LTV: no contiene un diccionario Document Security Store (DSS)");
299 				return new PDFSignature(fileTemp);
300 			}
301 			
302 			//-- Comprobar que existe el sello de tiempos para el documento
303 			boolean encontrado = false;
304 			for (Iterator iterator = names.iterator(); iterator.hasNext();) {
305 				String name = (String) iterator.next();
306 				if (af.getSignatureDictionary(name) != null && af.getSignatureDictionary(name).get(PdfName.SUBFILTER) != null &&
307 						af.getSignatureDictionary(name).get(PdfName.SUBFILTER).equals(new PdfName("ETSI.RFC3161"))) {
308 					encontrado = true;
309 					break;
310 				}
311 			}
312 			if (!encontrado) {
313 				logger.debug("[BasePDFSignature.getPDFObject]::El documento PDF no es PAdES-LTV: no contiene un sello de tiempos para todo el documento)");
314 				return new PDFSignature(fileTemp);
315 			} else {
316 				return new PAdESLTVSignature(fileTemp);
317 			}
318 			
319 		} catch (Exception e) {
320 			logger.info("[BasePDFSignature.getPDFObject]::Ha ocurrido un error cargando el objeto", e);
321 			throw new SignatureException ("El documento PDF no está firmado");
322 		}
323 	}
324 	
325 	/**
326 	 * Método para poder validar con el método Signature.validateSignature.<br><br>
327 	 * 
328 	 * Analiza el parámetro y, si se trata de un objeto PDF, devuelve un 
329 	 * objeto del tipo adecuado.
330 	 * 
331 	 * @param bSignature Firma como array de bytes
332 	 * @return PDF o PAdES
333 	 * @throws Exception El parámetro no es un PDF
334 	 */
335 	public static ISignature getSignatureInstance (byte[] bSignature) throws Exception {
336 		return getPDFObject(bSignature);
337 	}
338 	
339 	/**
340 	 * Método que se encargará de borrar el fichero temporal generado por
341 	 * este objeto.
342 	 */
343 	public void close() {
344 		logger.debug("[BasePDFSignature.close]::Eliminando el fichero temporal: " + pdfFile);
345 		pdfFile.delete();
346 		logger.debug("[BasePDFSignature.close]::Fichero temporal eliminado: " + pdfFile);
347 	}
348 	
349 	//-- Métodos protected
350 	
351 	/*
352 	 * Se pasa una cadena de certificación, se devolverá el que no pertenece a una CA.
353 	 */
354 	protected static Certificate getSigningCertificate(java.security.cert.Certificate[] arrayCertificates) throws NormalizeCertificateException {
355 		
356 		//-- Comprobar que no sea un autofirmado
357 		if (arrayCertificates.length == 1) {
358 			Certificate cert;
359 			try {
360 				cert = new Certificate (Util.getCertificate(arrayCertificates[0].getEncoded()));
361 			} catch (CertificateEncodingException e) {
362 				throw new NormalizeCertificateException("No es posible obtener el certificado", e);
363 			}
364 			if (cert.isSelfSigned()) {
365 				logger.debug("El certificado de firma es un autofirmado");
366 				return cert;
367 			}
368 		}
369 		
370 		//-- No es un autofirmado
371 		HashMap<String, Certificate> hmCertificates = new HashMap<String, Certificate>();
372 		Certificate[] certificates = new Certificate[arrayCertificates.length];
373 		for (int i = 0; i < arrayCertificates.length; i++) {
374 			try {
375 				certificates[i] = new Certificate (Util.getCertificate(arrayCertificates[i].getEncoded()));
376 			} catch (CertificateEncodingException e) {
377 				throw new NormalizeCertificateException("No es posible obtener el certificado", e);
378 			}
379 			hmCertificates.put(certificates[i].getSubjectDN(), certificates[i]);
380 		}
381 		
382 		//-- Se mira el padre de todos y se quitan los padres del map
383 		for (int i = 0; i < certificates.length; i++) {
384 			hmCertificates.remove(certificates[i].getIssuerDN());
385 			
386 			//-- Si el certificado es de OCSP también hay que quitarlo
387 			try {
388 				List<String>  extendedKeyUsages = certificates[i].getExtendedKeyUsage();
389 				if (extendedKeyUsages.contains(KeyPurposeId.id_kp_OCSPSigning.getId())) {
390 					hmCertificates.remove(certificates[i].getSubjectDN());
391 				}
392 			} catch (CertificateException e) {
393 				logger.debug("No es posible obtener los usos de clave extendidos del certificado: " + certificates[i].getCommonName());
394 			}
395 		}
396 		
397 		//-- Si no queda ninguno devolver null
398 		if (hmCertificates.isEmpty()) {	
399 			return null;
400 		}
401 		
402 		return hmCertificates.get(hmCertificates.keySet().iterator().next());
403 	}
404 
405 	/**
406 	 * Devuelve los nombres de los campos de firma. No se incluyen los nombres
407 	 * de los campos de firma que realmente son sellos de tiempo para el documento.
408 	 * 
409 	 * @param af AcroFields
410 	 * @return Nombres de los campos reales de firma
411 	 */
412 	protected static ArrayList<String> getRealSignatureNames(AcroFields af) {
413 		
414 		ArrayList<String> originalNames = af.getSignatureNames();
415 		ArrayList<String> realNames = new ArrayList<String>();
416 		for (int i = originalNames.size() - 1; i > -1; i--) {
417 			String name = (String) originalNames.get(i);
418 			if (af.getSignatureDictionary(name).get(PdfName.SUBFILTER) == null ||
419 					!af.getSignatureDictionary(name).get(PdfName.SUBFILTER).equals(new PdfName("ETSI.RFC3161"))) {
420 				realNames.add(name);
421 			}
422 
423 		}
424 		
425 		return realNames;
426 	}
427 
428 
429 	/*
430 	 * Obtiene un fichero temporal en el directorio temporal de Arangi
431 	 */
432 	protected static File getFileTemp() throws IOException {
433 		File temp = File.createTempFile("signed", "pdf", getArangiTemporalFolder());
434 		temp.deleteOnExit();
435 		
436 		return temp;
437 	}
438 
439 	/*
440 	 * Valida un certificado en base a unas respuestas OCSP y un sello de tiempos (los devuelve iText del
441 	 * objeto pkcs7
442 	 */
443 	protected static int validateFromCAdES(List<BasicOCSPResp> basicOcspResponses, Certificate certificadoAValidar,
444 			Certificate certificadoFirma, TimeStamp ts) throws MalformedTimeStampException {
445 		Certificate certificadoTS = ts.getSignatureCertificate();
446 		if (basicOcspResponses != null) {
447 			for(BasicOCSPResp bResp : basicOcspResponses) {
448 				CertificateOCSPResponse[] responses = OCSPResponse.getResponses(bResp);
449 				if (responses != null && responses.length > 0 && responses[0].match(certificadoAValidar)) {
450 					
451 					if (responses[0].getValidityPeriodBeginning().after(ts.getTime()) ||
452 							responses[0].getValidityPeriodEnd().before(ts.getTime())) {
453 						return ValidationResult.RESULT_TIMESTAMP_AFTER_VALIDITY_ITEM;
454 					}
455 					
456 					//-- Comprobar el estado de la respuesta
457 					if (responses[0].getStatus() != ValidationResult.RESULT_VALID) {
458 						if(certificadoAValidar.equals(certificadoFirma) || (certificadoTS != null && certificadoAValidar.equals(certificadoTS))) {
459 							return responses[0].getStatus();
460 						} else {
461 							return ValidationResult.RESULT_CERTIFICATE_CHAIN_VALIDATION_INVALID;
462 						}
463 					} 
464 					
465 					if (responses[0].getValidityPeriodEnd() != null && 
466 							responses[0].getValidityPeriodEnd().before(ts.getTime())) {
467 						//-- Comprobar que la fecha de la respuesta OCSP sea posterior a la fecha (del sello de tiempos del documento 
468 						//-- o del sello de tiempos de la firma)
469 						logger.debug("[PAdESLTVSignature.isValid]::La fecha de la respuesta OCSP (" + responses[0].getValidityPeriodEnd() + 
470 								") es anterior a la fecha de comprobación (" + ts.getTime() + ")");
471 						if(certificadoAValidar.equals(certificadoFirma) || (certificadoTS != null && certificadoAValidar.equals(certificadoTS))) {
472 							return ValidationResult.RESULT_TIMESTAMP_AFTER_VALIDITY_ITEM;
473 						} else {
474 							return ValidationResult.RESULT_CERTIFICATE_CHAIN_VALIDATION_INVALID;
475 						}
476 					}
477 					//-- Respuesta OCSP OK
478 					logger.debug("[PAdESLTVSignature.isValid]::La respuesta OCSP es correcta para el certificado de CN=" + certificadoFirma.getCommonName());
479 					return ValidationResult.RESULT_VALID;
480 				}
481 			}
482 		}
483 		
484 		return ValidationResult.RESULT_CERTIFICATE_CANNOT_BE_VALIDATED;
485 		
486 	}
487 }