All Downloads are FREE. Search and download functionalities are using the official Maven repository.

es.gob.afirma.signers.pades.PAdESTriPhaseSigner Maven / Gradle / Ivy

There is a newer version: 1.8.2
Show newest version
/* Copyright (C) 2011 [Gobierno de Espana]
 * This file is part of "Cliente @Firma".
 * "Cliente @Firma" is free software; you can redistribute it and/or modify it under the terms of:
 *   the GNU General Public License as published by the Free Software Foundation;
 *     either version 2 of the License, or (at your option) any later version.
 *   or The European Software License; either version 1.1 or (at your option) any later version.
 * You may contact the copyright holder at: [email protected]
 */

package es.gob.afirma.signers.pades;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.util.GregorianCalendar;
import java.util.Properties;

import com.aowagie.text.pdf.PdfDictionary;
import com.aowagie.text.pdf.PdfName;
import com.aowagie.text.pdf.PdfSignatureAppearance;
import com.aowagie.text.pdf.PdfString;

import es.gob.afirma.core.AOException;
import es.gob.afirma.core.misc.AOUtil;
import es.gob.afirma.core.signers.AOSignConstants;
import es.gob.afirma.core.signers.AdESPolicy;
import es.gob.afirma.core.signers.SignEnhancer;
import es.gob.afirma.signers.cades.CAdESSignerMetadataHelper;
import es.gob.afirma.signers.cades.CAdESTriPhaseSigner;
import es.gob.afirma.signers.cades.CommitmentTypeIndicationsHelper;
import es.gob.afirma.signers.tsp.pkcs7.CMSTimestamper;
import es.gob.afirma.signers.tsp.pkcs7.TsaParams;

/** Clase para la firma electrónica en tres fases de ficheros Adobe PDF en formato PAdES.
 * 

No firma PDF cifrados.

*

Necesita iText 2.1.7 con modificaciones específicas.

*

Esta clase no interacciona directamente en ningún momento con el usuario ni usa interfaces gráficos.

*

La firma electrónica en tres fases está pensada para entornos donde la clave privada reside * en un sistema con al menos alguna de las siguientes restricciones:

*
    *
  • * El sistema no es compatible con el Cliente @firma. En este caso, dado que el 95% del código se * ejecuta en un sistema externo, solo es necesario portar el 5% restante. *
  • *
  • * El sistema tiene unas capacidades muy limitadas en cuanto a proceso computacional, memoria o comunicaciones por * red. En este caso, el sistema solo realiza una operación criptográfica, una firma PKCS#1, * mucho menos demandante de potencia de proceso que una firma completa PAdES, y, adicionalmente, no trata el * documento a firmar completo, sino úicamente una prqueña cantidad de datos resultante de un * pre-proceso (la pre-firma) realizado por el sistema externo, lo que resulta en un enorme decremento en las necesidades * de memoria y transmisión de datos. *
  • *
  • * Por motivos de seguridad, el documento a firmar no puede salir de un sistema externo. Como se ha descrito en el punto * anterior, en este caso el documento jamás sale del sistema externo, sino que se transfiere únicamente * el resultado de la pre-firma, desde la cual es imposible reconstruir el documento original. *
  • *
*

* Estos condicionantes convierten la firma trifásica en una opción perfectamente adaptada a los * dispositivos móviles, donde se dan tanto la heterogeneidad de sistemas operativos (Apple iOS, Google * Android, RIM BlackBerry, Microsoft Windows Phone, etc.) y las limitaciones en potencia de proceso, memoria * y comunicaciones; en estas últimas hay que tener en cuenta el coste, especialmente si estamos haciendo * uso de una red de otro operador en itinerancia (roaming). *

*

* El funcionamiento típico de una firma trifásica en la que intervienen un disposotivo móvil, * un servidor Web (que hace la pre-firma y la post-firma) y un servidor documental podría ser el siguiente: *

*

Pre-firma:

*

Pre-firma

*
    *
  • El dispositivo móvil solicita una pre-firma al servidor Web indicando un identificador de documento.
  • *
  • El servidor Web solicita el documento a servidor documental.
  • *
  • * El servidor documental entrega el documento al servidor Web.
    Es importante recalcar que el servidor * documental no necesita almacenar ningún dato de sesión y que este no está expuesto a Internet * de forma directa en ningún momento. *
  • *
  • * El servidor Web calcula la pre-firma, entregando el resultado (muy pequeño en tamaño) al dispositivo.
    * Es importante recalcar que el servidor Web no necesita almacenar ningún dato de sesión ni * exponer los documentos directamente al dispositivo. *
  • *
*

Firma:

*

Firma

*
    *
  • * El dispositivo móvil realiza, de forma completamente aislada una firma electrónica * simple (computacionalmente ligera) de los datos de la pre-firma. La clave privada del usuario nunca sale * del dispositivo y no se expone externamente en ningún momento. *
  • *
*

Post-firma:

*

Post-firma

*
    *
  • * El dispositivo móvil solicita una post-firma al servidor Web indicando un identificador de * documento y proporcionando el resultado de su pre-firma firmada. *
  • *
  • El servidor Web solicita el documento a servidor documental.
  • *
  • El servidor documental entrega el documento al servidor Web.
  • *
  • * El servidor Web calcula la post-firma y compone el documento final firmado, entregando el resultado * al servidor documental para su almacén. *
  • *
  • El servidor documental almacena el nuevo documento y devuelve un identificador al servidor Web.
  • *
  • * El servidor Web comunica al dispositivo el éxito de la operación y el identificador del fichero * ya firmado y almacenado. *
  • *
*

* Es conveniente tener en cuenta al usar firmas trifásicas que es necesario disponer de un mecanismo * para que el usuario pueda ver en todo momento los documentos que está firmando (una copia que refleje * con fidelidad el contenido firmado puede ser suficiente) para evitar situaciones de repudio. *

*

* Una pecualiaridad de las firmas trifásicas PAdES es que en la generación o firma de un PDF se genera de forma * automática un identificador único y aleatorio llamado FILE_ID, que hace que al firmar en momentos diferentes * dos PDF exactamente iguales se generen PDF con un FILE_ID distinto, y, por lo tanto, con la huella * digital de la firma electrónica distinta.
* Para solventar este inconveniente, en la firma trifásica PDF, se considera prefirma tanto la totalidad de los atributos * CAdES a firmar como el FILE_ID del PDF que se debe compartir entre pre-firma y post-firma. *

* Notas sobre documentos certificados:
* Si un PDF firmado se ha certificado (por ejemplo, añadiendo una firma electrónica usando Adobe Reader), cualquier * modificación posterior del fichero (como la adición de nuevas firmas con este método) invalidará * las firmas previamente existentes.
* Consulte la documentación de la opción allowSigningCertifiedPdfs para establecer un comportamiento por * defecto respecto a los PDF certificados. * @author Tomás García-Merás */ public final class PAdESTriPhaseSigner { private static final String PDF_OID = "1.2.826.0.1089.1.5"; //$NON-NLS-1$ private static final String PDF_DESC = "Documento en formato PDF"; //$NON-NLS-1$ /** Referencia a la última página del documento PDF. */ public static final int LAST_PAGE = -1; /** Versión de iText necesaria para el uso de esta clase (2.1.7). */ public static final String ITEXT_VERSION = "2.1.7"; //$NON-NLS-1$ private static final int CSIZE = 27000; private PAdESTriPhaseSigner() { // No permitimos la instanciacion } /** Obtiene la pre-firma PAdES/CAdES de un PDF (atributos CAdES a firmar) * @param digestAlgorithmName Nombre del algoritmo de huella digital usado para la firma. * Debe usarse exactamente el mismo valor en la post-firma. *

Se aceptan los siguientes algoritmos en el parámetro digestAlgorithmName:

*
    *
  • SHA1
  • *
  • SHA-256
  • *
  • SHA-384
  • *
  • SHA-512
  • *
* @param inPDF PDF a firmar. Debe usarse exactamente el mismo documento en la post-firma. * @param signerCertificateChain Cadena de certificados del firmante. * Debe usarse exactamente la misma cadena de certificados en la post-firma. * @param xParams Parámetros adicionales para la firma (detalle). * Deben usarse exactamente los mismos valores en la post-firma. * @param signTime Momento de la firma. Debe usarse exactamente el mismo valor en la post-firma. * @return pre-firma CAdES/PAdES (atributos CAdES a firmar) * @throws IOException En caso de errores de entrada / salida * @throws AOException En caso de cualquier otro tipo de error * @throws InvalidPdfException En caso de errores al generar los datos de sesión */ public static PdfSignResult preSign(final String digestAlgorithmName, final byte[] inPDF, final Certificate[] signerCertificateChain, final GregorianCalendar signTime, final Properties xParams) throws IOException, AOException, InvalidPdfException { final Properties extraParams = xParams != null ? xParams : new Properties(); final PdfTriPhaseSession ptps = PdfSessionManager.getSessionData(inPDF, signerCertificateChain, signTime, extraParams); // La norma PAdES establece que si el algoritmo de huella digital es SHA1 debe usarse SigningCertificate, y en cualquier // otro caso deberia usarse SigningCertificateV2 boolean signingCertificateV2; if (extraParams.containsKey(PdfExtraParams.SIGNING_CERTIFICATE_V2)) { signingCertificateV2 = Boolean.parseBoolean(extraParams.getProperty(PdfExtraParams.SIGNING_CERTIFICATE_V2)); } else { signingCertificateV2 = !"SHA1".equals(AOSignConstants.getDigestAlgorithmName(digestAlgorithmName)); //$NON-NLS-1$ } final byte[] original = AOUtil.getDataFromInputStream(ptps.getSAP().getRangeStream()); // Calculamos el MessageDigest final byte[] md; try { md = MessageDigest.getInstance(AOSignConstants.getDigestAlgorithmName(digestAlgorithmName)).digest(original); } catch (final NoSuchAlgorithmException e) { throw new AOException("El algoritmo de huella digital no es valido: " + e, e); //$NON-NLS-1$ } // Pre-firma CAdES return new PdfSignResult( ptps.getFileID(), CAdESTriPhaseSigner.preSign( AOSignConstants.getDigestAlgorithmName(digestAlgorithmName), // Algoritmo de huella digital null, // Datos a firmar (null por ser explicita)) signerCertificateChain, // Cadena de certificados del firmante AdESPolicy.buildAdESPolicy(extraParams), // Politica de firma signingCertificateV2, // signingCertificateV2 md, // Valor de la huella digital del contenido signTime.getTime(), // Fecha de la firma (debe establecerse externamente para evitar desincronismos en la firma trifasica) false, // En PAdES nunca se incluye el SigningTime en la CAdES contenida true, // Modo PAdES PDF_OID, PDF_DESC, CommitmentTypeIndicationsHelper.getCommitmentTypeIndications(extraParams), CAdESSignerMetadataHelper.getCAdESSignerMetadata(extraParams), Boolean.parseBoolean(extraParams.getProperty(PdfExtraParams.DO_NOT_INCLUDE_POLICY_ON_SIGNING_CERTIFICATE, "false")) //$NON-NLS-1$ ), null, // Sello de tiempo signTime, extraParams ); } /** Post-firma en PAdES un documento PDF a partir de una pre-firma y la firma PKCS#1, generando un PDF final completo. * @param digestAlgorithmName Nombre del algoritmo de huella digital usado para la firma (debe ser el mismo que el usado en la pre-firma). *

Se aceptan los siguientes algoritmos en el parámetro digestAlgorithmName:

*
    *
  • SHA1
  • *
  • SHA-256
  • *
  • SHA-384
  • *
  • SHA-512
  • *
* @param inPdf PDF a firmar (debe ser el mismo que el usado en la pre-firma). * @param signerCertificateChain Cadena de certificados del firmante (debe ser la misma que la usado en la pre-firma). * @param pkcs1Signature Resultado de la firma PKCS#1 v1.5 de los datos de la pre-firma. * @param preSign Resultado de la pre-firma * @param enhancer Manejador para la generación de nuevos modos de firma (con * sello de tiempo, archivo longevo, etc.) * @param enhancerConfig Configuración para generar el nuevo modo de firma. * @return PDF firmado. * @throws AOException en caso de cualquier tipo de error. * @throws IOException Cuando ocurre algun error en la conversión o generación * de estructuras. * @throws NoSuchAlgorithmException Si hay problemas con el algoritmo durante el sello de tiempo. */ public static byte[] postSign(final String digestAlgorithmName, final byte[] inPdf, final Certificate[] signerCertificateChain, final byte[] pkcs1Signature, final PdfSignResult preSign, final SignEnhancer enhancer, final Properties enhancerConfig) throws AOException, IOException, NoSuchAlgorithmException { // Obtenemos la firma final PdfSignResult completePdfSSignature = generatePdfSignature( digestAlgorithmName, signerCertificateChain, preSign.getExtraParams(), pkcs1Signature, preSign.getSign(), preSign.getFileID(), preSign.getTimestamp(), preSign.getSignTime(), enhancer, enhancerConfig ); // Insertamos la firma en el PDF return insertSignatureOnPdf( inPdf, signerCertificateChain, completePdfSSignature ); } private static PdfSignResult generatePdfSignature(final String digestAlgorithmName, final Certificate[] signerCertificateChain, final Properties xParams, final byte[] pkcs1Signature, final byte[] signedAttributes, final String pdfFileId, final byte[] timestamp, final GregorianCalendar signingTime, final SignEnhancer enhancer, final Properties enhancerConfig) throws AOException, IOException, NoSuchAlgorithmException { byte[] completeCAdESSignature = CAdESTriPhaseSigner.postSign( AOSignConstants.getDigestAlgorithmName(digestAlgorithmName), null, signerCertificateChain, pkcs1Signature, signedAttributes ); final Properties extraParams = xParams != null ? xParams : new Properties(); //************************************************** //***************** SELLO DE TIEMPO **************** // El sello a nivel de firma nunca se aplica si han pedido solo sello a nivel de documento if (!TsaParams.TS_DOC.equals(extraParams.getProperty(PdfExtraParams.TS_TYPE))) { TsaParams tsaParams; try { tsaParams = new TsaParams(extraParams); } catch(final Exception e) { tsaParams = null; } if (tsaParams != null) { completeCAdESSignature = new CMSTimestamper(tsaParams).addTimestamp( completeCAdESSignature, tsaParams.getTsaHashAlgorithm(), signingTime ); } } //************** FIN SELLO DE TIEMPO **************** //*************************************************** if (enhancer != null) { completeCAdESSignature = enhancer.enhance( completeCAdESSignature, enhancerConfig != null ? enhancerConfig : extraParams ); } return new PdfSignResult( pdfFileId, completeCAdESSignature, timestamp, // Sello de tiempo signingTime, xParams != null ? xParams : new Properties()); } private static byte[] insertSignatureOnPdf(final byte[] inPdf, final Certificate[] signerCertificateChain, final PdfSignResult signature) throws AOException, IOException { final byte[] outc = new byte[CSIZE]; if (signature.getSign().length > CSIZE) { throw new AOException( "El tamano de la firma (" + signature.getSign().length + ") supera el maximo permitido para un PDF (" + CSIZE + ")" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ); } final PdfDictionary dic2 = new PdfDictionary(); System.arraycopy(signature.getSign(), 0, outc, 0, signature.getSign().length); dic2.put(PdfName.CONTENTS, new PdfString(outc).setHexWriting(true)); final PdfTriPhaseSession pts; try { pts = PdfSessionManager.getSessionData(inPdf, signerCertificateChain, signature.getSignTime(), signature.getExtraParams()); } catch (final InvalidPdfException e) { throw new IOException(e); } final PdfSignatureAppearance sap = pts.getSAP(); final byte[] ret; try ( final ByteArrayOutputStream baos = pts.getBAOS(); ) { final String badFileID = pts.getFileID(); try { sap.close(dic2); } catch (final Exception e) { baos.close(); throw new AOException("Error al cerrar el PDF para finalizar el proceso de firma", e); //$NON-NLS-1$ } ret = new String(baos.toByteArray(), "ISO-8859-1").replace(badFileID, signature.getFileID()).getBytes("ISO-8859-1"); //$NON-NLS-1$ //$NON-NLS-2$ } return ret; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy