es.gob.afirma.signers.pades.AOPDFSigner Maven / Gradle / Ivy
/* 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.ByteArrayInputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.logging.Logger;
import com.aowagie.text.exceptions.BadPasswordException;
import com.aowagie.text.pdf.AcroFields;
import com.aowagie.text.pdf.PdfDictionary;
import com.aowagie.text.pdf.PdfName;
import com.aowagie.text.pdf.PdfPKCS7;
import com.aowagie.text.pdf.PdfReader;
import es.gob.afirma.core.AOCancelledOperationException;
import es.gob.afirma.core.AOException;
import es.gob.afirma.core.AOInvalidFormatException;
import es.gob.afirma.core.misc.AOUtil;
import es.gob.afirma.core.signers.AOPkcs1Signer;
import es.gob.afirma.core.signers.AOSignConstants;
import es.gob.afirma.core.signers.AOSignInfo;
import es.gob.afirma.core.signers.AOSigner;
import es.gob.afirma.core.signers.AOSimpleSignInfo;
import es.gob.afirma.core.signers.CounterSignTarget;
import es.gob.afirma.core.signers.SignEnhancer;
import es.gob.afirma.core.ui.AOUIFactory;
import es.gob.afirma.core.util.tree.AOTreeModel;
import es.gob.afirma.core.util.tree.AOTreeNode;
/** Manejador de firmas binarias de ficheros Adobe PDF en formato PAdES.
* Para compatibilidad estricta con PAdES-BES/EPES se utiliza ETSI.CAdES.detached como nombre del subfiltro.
* La compatibilidad con PAdES no es completa, omitiéndose los siguientes aspectos de la normativa:
*
* - Firma separada de ficheros empotrados en el documento PDF.
* - Firma separada de ficheros adjuntos al documento PDF.
*
*
* Estas mismas deficiencias provocan igualmente la incompatibilidad de las firmas generadas con "Carpetas PDF" (Portfolios PDF).
* Cuando se encuentran documentos PDF con ficheros adjuntos o empotrados se imprime información relativa en consola.
*
*
* Por compatibilidad con Adobe Reader, la firmas se generan con el subfiltro "adbe.pkcs7.detached" en vez de con
* "ETSI.CAdES.detached". Consulte la documentación del parámetro signatureSubFilter
para variar este comportamiento.
*
*
* La clase necesita específicamente la versión de iText 2.1.7 modificada para el Cliente @firma.
*
*/
public final class AOPDFSigner implements AOSigner {
private static final String PDF_FILE_SUFFIX = ".pdf"; //$NON-NLS-1$
private static final String PDF_FILE_HEADER = "%PDF-"; //$NON-NLS-1$
private static final Logger LOGGER = Logger.getLogger("es.gob.afirma"); //$NON-NLS-1$
private static final PdfName PDFNAME_ETSI_RFC3161 = new PdfName("ETSI.RFC3161"); //$NON-NLS-1$
private static final PdfName PDFNAME_DOCTIMESTAMP = new PdfName("DocTimeStamp"); //$NON-NLS-1$
/** Tamaño mínimo de un PDF.
*
* https://stackoverflow.com/questions/17279712/what-is-the-smallest-possible-valid-pdf
* . */
private static final int PDF_MIN_FILE_SIZE = 70;
private static SignEnhancer enhancer = null;
private static Properties enhancerConfig;
static {
enhancerConfig = new Properties();
String enhancerClassName = null;
try {
enhancerConfig.load(
AOPDFSigner.class.getResourceAsStream("/enhancer.properties") //$NON-NLS-1$
);
enhancerClassName = enhancerConfig.getProperty("enhancerClassFile"); //$NON-NLS-1$
if (enhancerClassName != null) {
enhancer = (SignEnhancer) Class.forName(enhancerClassName).getConstructor().newInstance();
LOGGER.info("Se usara el siguiente mejorador de firmas: " + enhancerClassName); //$NON-NLS-1$
}
}
catch(final ClassNotFoundException e) {
LOGGER.warning(
"Se ha configurado la clase de mejora '" + enhancerClassName + "', pero esta no se encuentra: " + e //$NON-NLS-1$//$NON-NLS-2$
);
}
catch (final Exception e) {
LOGGER.info("No hay un mejorador de firmas correctamente instalado: " + e); //$NON-NLS-1$
}
}
/** Obtiene el mejorador de firmas por defecto.
* @return Mejorador de firmas por defecto. */
public static SignEnhancer getSignEnhancer() {
return enhancer;
}
/** Obtiene la configuración del mejorador de firmas por defecto.
* @return Configuración del mejorador de firmas por defecto. */
public static Properties getSignEnhancerConfig() {
return enhancerConfig;
}
/** Firma un documento PDF en formato PAdES.
*
* Notas sobre documentos certificados:
* Si un PDF firmado se ha certificado (por ejemplo, añadiendo una firma electrónica usando Adobe Acrobat), cualquier
* modificación posterior del fichero (como la adición de nuevas firmas con este método) invalidará
* las firmas previamente existentes.
* Si se detecta un documento PDF certificado, se mostrará un diálogo gráfico advirtiendo al usuario de esta
* situación y pidiendo confirmación para continuar.
Si desea evitar interacciones directas con los usuarios
* consulte la documentación de las opciones allowSigningCertifiedPdfs
y headless
.
*
*
* Notas sobre documentos protegidos con contraseña:
* Si un PDF está protegido con contraseña por estar cifrado, se mostrará un diálogo gráfico advirtiendo al usuario de esta
* situación y solicitando la contraseña de apertura del PDF.
Si desea evitar interacciones directas con los usuarios
* consulte la documentación de las opciones ownerPassword
y headless
.
* Adicionalmente, si el fichero de entrada estaba cifrado y protegido con contraseña, la salida seráa un documento PDF
* igualmente cifrado y protegido con contraseña..
*
* @param inPDF Documento PDF a firmar.
* @param algorithm Algoritmo a usar para la firma.
* Se aceptan los siguientes algoritmos en el parámetro algorithm
:
*
* - SHA1withRSA
* - SHA256withRSA
* - SHA384withRSA
* - SHA512withRSA
*
* @param key Clave privada a usar para firmar.
* @param certChain Cadena de certificados del firmante.
* @param xParams Parámetros adicionales para la firma (detalle).
* @return Documento PDF firmado en formato PAdES.
* @throws AOException Cuando ocurre cualquier problema durante el proceso.
* @throws IOException Cuando hay errores en el tratamiento de datos. */
@Override
public byte[] sign(final byte[] inPDF,
final String algorithm,
final PrivateKey key,
final java.security.cert.Certificate[] certChain,
final Properties xParams) throws AOException,
IOException {
final Properties extraParams = xParams != null ? xParams : new Properties();
final java.security.cert.Certificate[] certificateChain = Boolean.parseBoolean(extraParams.getProperty(PdfExtraParams.INCLUDE_ONLY_SIGNNING_CERTIFICATE, Boolean.FALSE.toString())) ?
new X509Certificate[] { (X509Certificate) certChain[0] } :
certChain;
final GregorianCalendar signTime = PdfUtil.getSignTime(extraParams.getProperty(PdfExtraParams.SIGN_TIME));
// Sello de tiempo
byte[] data;
try {
data = PdfTimestamper.timestampPdf(inPDF, extraParams, signTime);
}
catch (final NoSuchAlgorithmException e1) {
throw new IOException(
"No se soporta el algoritmo indicado para la huella digital del sello de tiempo: " + e1, e1 //$NON-NLS-1$
);
}
// Prefirma
final PdfSignResult pre;
try {
pre = PAdESTriPhaseSigner.preSign(
algorithm,
data,
certificateChain,
signTime,
extraParams
);
}
catch (final InvalidPdfException e) {
throw e;
}
// Firma PKCS#1
final byte[] interSign;
try {
interSign = new AOPkcs1Signer().sign(
pre.getSign(),
algorithm,
key,
certificateChain,
extraParams
);
}
catch (final Exception e) {
if ("es.gob.jmulticard.CancelledOperationException".equals(e.getClass().getName())) { //$NON-NLS-1$
throw new AOCancelledOperationException();
}
throw new AOException("Error durante la firma PAdES: " + e, e); //$NON-NLS-1$
}
// Postfirma
try {
return PAdESTriPhaseSigner.postSign(
algorithm,
data,
certificateChain,
interSign,
pre,
getSignEnhancer(), // SignEnhancer
getSignEnhancerConfig() // EnhancerConfig (si le llega null usa los ExtraParams)
);
}
catch (final NoSuchAlgorithmException e) {
throw new AOException("Error el en algoritmo de firma: " + e, e); //$NON-NLS-1$
}
}
/** Añade una firma PAdES a un documento PDF. El comportamiento es exactamente el mismo que una llamada al método sign(...)
* puesto que las multifirmas en los ficheros PDF se limitan a firmas independientes "en serie", pero no implementando los mecanismos de
* cofirma o contrafirma de CAdES.
*
* 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.
* Si se detecta un documento PDF certificado, se mostrará un diálogo gráfico advirtiendo al usuario de esta
* situación y pidiendo confirmación para continuar.
Si desea evitar interacciones directas con los usuarios
* consulte la documentación de las opciones allowSigningCertifiedPdfs
y headless
.
*
*
* Notas sobre documentos protegidos con contraseña:
* Si un PDF está protegido con contraseña por estar cifrado, se mostrará un diálogo gráfico advirtiendo al usuario de esta
* situación y solicitando la contraseña de apertura del PDF.
Si desea evitar interacciones directas con los usuarios
* consulte la documentación de las opciones ownerPassword
y headless
.
* Adicionalmente, si el fichero de entrada estaba cifrado y protegido con contraseña, la salida será un documento PDF
* igualmente cifrado y protegido con contraseña.
*
* En general, es recomendable prescindir de este método y llamar directamente al método sign(...)
.
* @param data Se ignora el valor de este parámetro. El documento PDF debe proporcionarse mediante el parátro sign
.
* @param sign Documento PDF a firmar.
* @param algorithm Algoritmo a usar para la firma.
* Se aceptan los siguientes algoritmos en el parámetro algorithm
:
*
* - SHA1withRSA
* - SHA256withRSA
* - SHA384withRSA
* - SHA512withRSA
*
* @param key Clave privada a usar para firmar.
* @param certChain Cadena de certificados del firmante.
* @param extraParams Parámetros adicionales para la firma (detalle).
* @return Documento PDF firmado en formato PAdES.
* @throws AOException Cuando ocurre cualquier problema durante el proceso.
* @throws IOException En caso de errores de entrada / salida. */
@Override
public byte[] cosign(final byte[] data,
final byte[] sign,
final String algorithm,
final PrivateKey key,
final java.security.cert.Certificate[] certChain,
final Properties extraParams) throws AOException, IOException {
return sign(sign, algorithm, key, certChain, extraParams);
}
/** Añade una firma PAdES a un documento PDF. El comportamiento es exactamente el mismo que una llamada al método sign(...)
* puesto que las multifirmas en los ficheros PDF se limitan a firmas independientes "en serie", pero no implementando los mecanismos de
* cofirma o contrafirma de CAdES.
*
* 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.
* Si se detecta un documento PDF certificado, se mostrará un diálogo gráfico advirtiendo al usuario de esta
* situación y pidiendo confirmación para continuar.
Si desea evitar interacciones directas con los usuarios
* consulte la documentación de las opciones allowSigningCertifiedPdfs
y headless
.
*
*
* Notas sobre documentos protegidos con contraseña:
* Si un PDF está protegido con contraseña por estar cifrado, se mostrará un diálogo gráfico advirtiendo al usuario de esta
* situación y solicitando la contraseña de apertura del PDF.
Si desea evitar interacciones directas con los usuarios
* consulte la documentación de las opciones ownerPassword
y headless
.
* Adicionalmente, si el fichero de entrada estaba cifrado y protegido con contraseña, la salida será un documento PDF
* igualmente cifrado y protegido con contraseña.
*
* En general, es recomendable prescindir de este método y llamar directamente al método sign(...)
* @param sign Documento PDF a firmar
* @param algorithm Algoritmo a usar para la firma.
* Se aceptan los siguientes algoritmos en el parámetro algorithm
:
*
* - SHA1withRSA
* - SHA256withRSA
* - SHA384withRSA
* - SHA512withRSA
*
* @param key Clave privada a usar para firmar.
* @param certChain Cadena de certificados del firmante.
* @param extraParams Parámetros adicionales para la firma (detalle).
* @return Documento PDF firmado en formato PAdES.
* @throws AOException Cuando ocurre cualquier problema durante el proceso.
* @throws IOException En caso de errores de entrada / salida. */
@Override
public byte[] cosign(final byte[] sign,
final String algorithm,
final PrivateKey key,
final java.security.cert.Certificate[] certChain,
final Properties extraParams) throws AOException, IOException {
return sign(sign, algorithm, key, certChain, extraParams);
}
/** Operación no soportada para firmas PAdES. */
@Override
public byte[] countersign(final byte[] sign,
final String algorithm,
final CounterSignTarget targetType,
final Object[] targets,
final PrivateKey key,
final java.security.cert.Certificate[] certChain,
final Properties extraParams) {
throw new UnsupportedOperationException("No es posible realizar contrafirmas de ficheros PDF"); //$NON-NLS-1$
}
/** Devuelve el nombre de fichero de firma predeterminado que se recomienda usar para
* un PDF firmado con nombre original igual al proporcionado.
* En este caso el resultado será siempre el nombre original más un
* sufijo adicional (opcional) previo a la extensión.
* Siempre se termina el nombre de fichero con la extensión .pdf, incluso si el nombre original carecía de esta.
* @param originalName Nombre del fichero original que se firma.
* @param inText Sufijo a agregar al nombre de fichero devuelto, inmediatamente anterior a la extensión.
* @return Nombre apropiado para el fichero de firma. */
@Override
public String getSignedName(final String originalName, final String inText) {
final String inTextInt = inText != null ? inText : ""; //$NON-NLS-1$
if (originalName == null) {
return "signed.pdf"; //$NON-NLS-1$
}
if (originalName.toLowerCase(Locale.US).endsWith(PDF_FILE_SUFFIX)) {
return originalName.substring(0, originalName.length() - PDF_FILE_SUFFIX.length()) + inTextInt + PDF_FILE_SUFFIX;
}
return originalName + inTextInt + PDF_FILE_SUFFIX;
}
/** Recupera el árbol de nodos de firma de una firma electrónica.
* Los nodos del árbol serán textos con el CommonName (CN X.500)
* del titular del certificado u objetos de tipo AOSimpleSignInfo con la
* información básica de las firmas individuales, según
* el valor del parámetro asSimpleSignInfo
. Los nodos se
* mostrarán en el mismo orden y con la misma estructura con el que
* aparecen en la firma electrónica.
* La propia estructura de firma se considera el nodo raíz, la firma y cofirmas
* penderán directamentede de este.
* @param sign Firma electrónica de la que se desea obtener la estructura.
* @param asSimpleSignInfo Si es true
se devuelve un árbol con la
* información básica de cada firma individual
* mediante objetos AOSimpleSignInfo
, si es false
* un árbol con los nombres (CN X.500) de los titulares certificados.
* @return Árbol de nodos de firma o null
en caso de error. */
@Override
public AOTreeModel getSignersStructure(final byte[] sign, final boolean asSimpleSignInfo) {
final AOTreeNode root = new AOTreeNode("Datos"); //$NON-NLS-1$
if (!isPdfFile(sign)) {
return new AOTreeModel(root);
}
PdfReader pdfReader;
try {
pdfReader = new PdfReader(sign);
}
catch (final BadPasswordException e) {
LOGGER.info(
"El PDF necesita contrasena: " + e //$NON-NLS-1$
);
try {
pdfReader = new PdfReader(
sign,
new String(
AOUIFactory.getPassword(
CommonPdfMessages.getString("AOPDFSigner.0"), //$NON-NLS-1$
null
)
).getBytes()
);
}
catch (final BadPasswordException e2) {
LOGGER.severe("La contrasena del PDF no es valida, se devolvera un arbol vacio: " + e2); //$NON-NLS-1$
return new AOTreeModel(root);
}
catch (final Exception e3) {
LOGGER.severe("No se ha podido leer el PDF, se devolvera un arbol vacio: " + e3); //$NON-NLS-1$
return new AOTreeModel(root);
}
}
catch (final Exception e) {
LOGGER.severe("No se ha podido leer el PDF, se devolvera un arbol vacio: " + e); //$NON-NLS-1$
return new AOTreeModel(root);
}
final AcroFields af;
try {
af = pdfReader.getAcroFields();
}
catch (final Exception e) {
LOGGER.severe("No se ha podido obtener la informacion de los firmantes del PDF, se devolvera un arbol vacio: " + e); //$NON-NLS-1$
return new AOTreeModel(root);
}
final List names = af.getSignatureNames();
for (final String signatureName : names) {
// Comprobamos si es una firma o un sello
final PdfDictionary pdfDictionary = af.getSignatureDictionary(signatureName);
if (PDFNAME_ETSI_RFC3161.equals(pdfDictionary.get(PdfName.SUBFILTER)) || PDFNAME_DOCTIMESTAMP.equals(pdfDictionary.get(PdfName.SUBFILTER))) {
// Ignoramos los sellos
continue;
}
final PdfPKCS7 pcks7;
try {
pcks7 = af.verifySignature(signatureName);
}
catch(final Exception e) {
LOGGER.severe(
"El PDF contiene una firma corrupta o con un formato desconocido (" + //$NON-NLS-1$
signatureName +
"), se continua con las siguientes si las hubiese: " + e //$NON-NLS-1$
);
continue;
}
if (asSimpleSignInfo) {
final X509Certificate[] certChain = new X509Certificate[pcks7.getSignCertificateChain().length];
for (int j = 0; j < certChain.length; j++) {
certChain[j] = (X509Certificate) pcks7.getSignCertificateChain()[j];
}
final AOSimpleSignInfo ssi = new AOSimpleSignInfo(
certChain,
pcks7.getSignDate().getTime()
);
// Extraemos el PKCS#1 de la firma
final byte[] pkcs1 = pcks7.getPkcs1();
if (pkcs1 != null) {
ssi.setPkcs1(pkcs1);
}
root.add(new AOTreeNode(ssi));
}
else {
root.add(new AOTreeNode(AOUtil.getCN(pcks7.getSigningCertificate())));
}
}
return new AOTreeModel(root);
}
/** Comprueba que los datos proporcionados sean un documento PDF.
* @param data Datos a comprobar.
* @return true
si los datos proporcionados son un documento PDF,
* false
en caso contrario. */
@Override
public boolean isSign(final byte[] data) {
if (data == null) {
LOGGER.warning("Se han introducido datos nulos para su comprobacion"); //$NON-NLS-1$
return false;
}
if (!isPdfFile(data)) {
return false;
}
final Object root = getSignersStructure(data, false).getRoot();
if (root instanceof AOTreeNode) {
// Si el arbol contiene firmas...
if (AOTreeModel.getChildCount(root) > 0) {
return true;
}
// Si no las contiene aun puede haber firmas no registradas
// Como el metodo no recibe "extraParams" buscamos en las propiedades de sistema
final Properties extraParams = System.getProperties();
try {
if (PdfUtil.pdfHasUnregisteredSignatures(data, extraParams) &&
Boolean.TRUE.toString().equalsIgnoreCase(extraParams.getProperty(PdfExtraParams.ALLOW_COSIGNING_UNREGISTERED_SIGNATURES))) {
return true;
}
}
catch (final Exception e) {
LOGGER.severe("No se han podido comprobar las firmas no registradas del PDF: " + e); //$NON-NLS-1$
}
}
return false;
}
private static boolean isPdfFile(final byte[] data) {
if (data == null || data.length < PDF_MIN_FILE_SIZE) {
return false;
}
final byte[] buffer = new byte[PDF_FILE_HEADER.length()];
try {
new ByteArrayInputStream(data).read(buffer);
}
catch (final Exception e) {
Logger.getLogger("es.gob.afirma").warning( //$NON-NLS-1$
"El contenido parece corrupto o truncado: " + e //$NON-NLS-1$
);
return false;
}
// Comprobamos que cuente con una cabecera PDF
if (!PDF_FILE_HEADER.equals(new String(buffer))) {
return false;
}
try {
// Si lanza una excepcion al crear la instancia, no es un fichero PDF
new PdfReader(data);
}
catch (final BadPasswordException e) {
LOGGER.warning("El PDF esta protegido con contrasena, se toma como PDF valido: " + e); //$NON-NLS-1$
return true;
}
catch (final Exception e) {
return false;
}
return true;
}
/** Comprueba que los datos proporcionados sean un documento PDF.
* @param data Datos a comprobar
* @return true
si los datos proporcionados son un documento PDF, false
en caso contrario */
@Override
public boolean isValidDataFile(final byte[] data) {
if (data == null) {
LOGGER.warning("Se han introducido datos nulos para su comprobacion"); //$NON-NLS-1$
return false;
}
return isPdfFile(data);
}
/** Obtiene el nombre con el que debería guardarse un PDF tras ser
* firmado. Básicamente se le anexa el sufijo .signed al
* nombre original, manteniendo la extensión (se respetan
* mayúculas y minúsculas en esta, pero no se admite una
* extensión con mezcla de ambas).
* @param originalName Nombre original del fichero PDF.
* @return Nombre recomendado para el PDF ya firmado. */
public static String getSignedName(final String originalName) {
if (originalName == null) {
return "signed.pdf"; //$NON-NLS-1$
}
if (originalName.endsWith(PDF_FILE_SUFFIX)) {
return originalName.replace(PDF_FILE_SUFFIX, ".signed.pdf"); //$NON-NLS-1$
}
if (originalName.endsWith(".PDF")) { //$NON-NLS-1$
return originalName.replace(".PDF", ".signed.pdf"); //$NON-NLS-1$ //$NON-NLS-2$
}
return originalName + ".signed.pdf"; //$NON-NLS-1$
}
/** Si la entrada es un documento PDF, devuelve el mismo documento PDF.
* @param sign Documento PDF
* @return Mismo documento PDF de entrada, sin modificar en ningú aspecto.
* @throws AOInvalidFormatException Si los datos de entrada no son un documento PDF. */
@Override
public byte[] getData(final byte[] sign) throws AOInvalidFormatException {
// Si no es una firma PDF valida, lanzamos una excepcion
if (!isSign(sign)) {
throw new AOInvalidFormatException("El documento introducido no contiene una firma valida"); //$NON-NLS-1$
}
// TODO: Devolver el PDF sin firmar
return sign;
}
/** Si la entrada es un documento PDF, devuelve un objeto AOSignInfo
* con el formato establecido a AOSignConstants.SIGN_FORMAT_PDF
.
* @param data Documento PDF.
* @return Objeto AOSignInfo
con el formato establecido a AOSignConstants.SIGN_FORMAT_PDF
.
* @throws AOException Si los datos de entrada no son un documento PDF. */
@Override
public AOSignInfo getSignInfo(final byte[] data) throws AOException {
if (data == null) {
throw new IllegalArgumentException("No se han introducido datos para analizar"); //$NON-NLS-1$
}
if (!isSign(data)) {
throw new AOInvalidFormatException("Los datos introducidos no se corresponden con un objeto de firma"); //$NON-NLS-1$
}
return new AOSignInfo(AOSignConstants.SIGN_FORMAT_PDF);
// Aqui podria venir el analisis de la firma buscando alguno de los
// otros datos de relevancia que se almacenan en el objeto AOSignInfo
}
/** Configura, cuando no lo esten ya, las propiedades necesarias para que las firmas
* sobre unos datos respeten el formato que tuviesen firmas anteriores.
* @param data Datos que se desean firmar.
* @param config Configuración establecida. */
public static void configureRespectfulProperties(final byte[] data, final Properties config) {
if (config != null && !config.containsKey(PdfExtraParams.SIGNATURE_SUBFILTER)) {
String filter;
try {
filter = PdfUtil.getFirstSupportedSignSubFilter(data, config);
}
catch (final Exception e) {
LOGGER.warning("Error al configurar la firma PDF para que sea igual a las existentes: " + e); //$NON-NLS-1$
return;
}
if (filter != null) {
config.setProperty(PdfExtraParams.SIGNATURE_SUBFILTER, filter.substring(filter.indexOf('/') + 1));
}
}
}
}