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

io.rubrica.sign.xades.FirmadorXAdES Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2009-2018 Rubrica
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 */

package io.rubrica.sign.xades;

import java.io.ByteArrayInputStream;
import java.net.URI;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.logging.Logger;

import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import es.uji.crypto.xades.jxades.security.xml.XAdES.CommitmentTypeIndication;
import es.uji.crypto.xades.jxades.security.xml.XAdES.DataObjectFormat;
import es.uji.crypto.xades.jxades.security.xml.XAdES.DataObjectFormatImpl;
import es.uji.crypto.xades.jxades.security.xml.XAdES.ObjectIdentifierImpl;
import es.uji.crypto.xades.jxades.security.xml.XAdES.XAdES;
import es.uji.crypto.xades.jxades.security.xml.XAdES.XAdES_EPES;
import io.rubrica.core.RubricaException;
import io.rubrica.core.Util;
import io.rubrica.sign.XMLConstants;
import io.rubrica.util.MimeHelper;
import io.rubrica.xml.Utils;

/**
 * Firmador simple XAdES.
 */
public final class FirmadorXAdES {

	private static final String HTTP_PROTOCOL_PREFIX = "http://";
	private static final String HTTPS_PROTOCOL_PREFIX = "https://";

	private static final Logger logger = Logger.getLogger(FirmadorXAdES.class.getName());

	/** Identificador de identificadores en los nodos XML. */
	static final String ID_IDENTIFIER = "Id";

	private FirmadorXAdES() {
		// No permitimos la instanciacion
	}

	/**
	 * Firma datos en formato XAdES.
	 * 

* Este método, al firmar un XML, firmas también sus hojas de * estilo XSL asociadas, siguiendo el siguiente criterio: *

    *
  • Firmas XML Enveloped *
      *
    • Hoja de estilo con ruta relativa *
        *
      • No se firma.
      • *
      *
    • *
    • Hola de estilo remota con ruta absoluta *
        *
      • Se restaura la declaración de hoja de estilo tal y como estaba en * el XML original
      • *
      • Se firma una referencia (canonicalizada) a esta hoja remota
      • *
      *
    • *
    • Hoja de estilo empotrada *
        *
      • Se restaura la declaración de hoja de estilo tal y como estaba en * el XML original
      • *
      *
    • *
    *
  • *
  • Firmas XML Externally Detached *
      *
    • Hoja de estilo con ruta relativa *
        *
      • No se firma.
      • *
      *
    • *
    • Hola de estilo remota con ruta absoluta *
        *
      • Se firma una referencia (canonicalizada) a esta hoja remota
      • *
      *
    • *
    • Hoja de estilo empotrada *
        *
      • No es necesaria ninguna acción
      • *
      *
    • *
    *
  • *
  • Firmas XML Enveloping *
      *
    • Hoja de estilo con ruta relativa *
        *
      • No se firma.
      • *
      *
    • *
    • Hola de estilo remota con ruta absoluta *
        *
      • Se firma una referencia (canonicalizada) a esta hoja remota
      • *
      *
    • *
    • Hoja de estilo empotrada *
        *
      • No es necesaria ninguna acción
      • *
      *
    • *
    *
  • *
  • Firmas XML Internally Detached *
      *
    • Hoja de estilo con ruta relativa *
        *
      • No se firma.
      • *
      *
    • *
    • Hola de estilo remota con ruta absoluta *
        *
      • Se firma una referencia (canonicalizada) a esta hoja remota
      • *
      *
    • *
    • Hoja de estilo empotrada *
        *
      • No es necesaria ninguna acción
      • *
      *
    • *
    *
  • *
* * @param data * Datos que deseamos firmar. * @param algorithm * Algoritmo a usar para la firma. *

* Se aceptan los siguientes algoritmos en el parámetro * algorithm: *

*
    *
  •    SHA1withRSA
  • *
  •    SHA256withRSA
  • *
  •    SHA384withRSA
  • *
  •    SHA512withRSA
  • *
* @param certChain * Cadena de certificados del firmante * @param pk * Clave privada del firmante * @param xParams * Parámetros adicionales para la firma * (detalle) * @return Firma en formato XAdES * @throws AOException * Cuando ocurre cualquier problema durante el proceso */ public static byte[] sign(byte[] data, String algorithm, PrivateKey pk, Certificate[] certChain, Properties xParams) throws RubricaException { String algoUri = XMLConstants.SIGN_ALGOS_URI.get(algorithm); if (algoUri == null) { throw new UnsupportedOperationException( "Los formatos de firma XML no soportan el algoritmo de firma '" + algorithm + "'"); } // *********************************************************************************************** // ********** LECTURA PARAMETROS ADICIONALES // ***************************************************** Properties extraParams = xParams != null ? xParams : new Properties(); boolean avoidXpathExtraTransformsOnEnveloped = Boolean.parseBoolean(extraParams .getProperty(XAdESExtraParams.AVOID_XPATH_EXTRA_TRANSFORMS_ON_ENVELOPED, Boolean.FALSE.toString())); boolean onlySignningCert = Boolean.parseBoolean( extraParams.getProperty(XAdESExtraParams.INCLUDE_ONLY_SIGNNING_CERTIFICATE, Boolean.FALSE.toString())); String envelopedNodeXPath = extraParams .getProperty(XAdESExtraParams.INSERT_ENVELOPED_SIGNATURE_ON_NODE_BY_XPATH); String nodeToSign = extraParams.getProperty(XAdESExtraParams.NODE_TOSIGN); String digestMethodAlgorithm = extraParams.getProperty(XAdESExtraParams.REFERENCES_DIGEST_METHOD, XAdESSigner.DIGEST_METHOD); String canonicalizationAlgorithm = extraParams.getProperty(XAdESExtraParams.CANONICALIZATION_ALGORITHM, CanonicalizationMethod.INCLUSIVE); if ("none".equalsIgnoreCase(canonicalizationAlgorithm)) { canonicalizationAlgorithm = null; } String xadesNamespace = extraParams.getProperty(XAdESExtraParams.XADES_NAMESPACE, XAdESSigner.XADESNS); String signedPropertiesTypeUrl = extraParams.getProperty(XAdESExtraParams.SIGNED_PROPERTIES_TYPE_URL, XAdESSigner.XADES_SIGNED_PROPERTIES_TYPE); boolean ignoreStyleSheets = Boolean .parseBoolean(extraParams.getProperty(XAdESExtraParams.IGNORE_STYLE_SHEETS, Boolean.FALSE.toString())); boolean avoidBase64Transforms = Boolean.parseBoolean( extraParams.getProperty(XAdESExtraParams.AVOID_BASE64_TRANSFORMS, Boolean.FALSE.toString())); boolean headless = Boolean .parseBoolean(extraParams.getProperty(XAdESExtraParams.HEADLESS, Boolean.TRUE.toString())); boolean addKeyInfoKeyValue = Boolean.parseBoolean( extraParams.getProperty(XAdESExtraParams.ADD_KEY_INFO_KEY_VALUE, Boolean.TRUE.toString())); boolean addKeyInfoKeyName = Boolean.parseBoolean( extraParams.getProperty(XAdESExtraParams.ADD_KEY_INFO_KEY_NAME, Boolean.FALSE.toString())); boolean addKeyInfoX509IssuerSerial = Boolean.parseBoolean( extraParams.getProperty(XAdESExtraParams.ADD_KEY_INFO_X509_ISSUER_SERIAL, Boolean.FALSE.toString())); String precalculatedHashAlgorithm = extraParams.getProperty(XAdESExtraParams.PRECALCULATED_HASH_ALGORITHM); boolean facturaeSign = Boolean .parseBoolean(extraParams.getProperty(XAdESExtraParams.FACTURAE_SIGN, Boolean.FALSE.toString())); String outputXmlEncoding = extraParams.getProperty(XAdESExtraParams.OUTPUT_XML_ENCODING); String mimeType = extraParams.getProperty(XAdESExtraParams.XMLDSIG_OBJECT_MIME_TYPE); String encoding = extraParams.getProperty(XAdESExtraParams.XMLDSIG_OBJECT_ENCODING); // Dejamos que indiquen "base64" en vez de la URI, hacemos el cambio // manualmente if ("base64".equalsIgnoreCase(encoding)) { encoding = XMLConstants.BASE64_ENCODING; } // Comprobamos que sea una URI if (encoding != null && !encoding.isEmpty()) { try { new URI(encoding); } catch (final Exception e) { throw new RubricaException("La codificacion indicada en 'encoding' debe ser una URI: " + e, e); } } final boolean keepKeyInfoUnsigned = Boolean.parseBoolean( extraParams.getProperty(XAdESExtraParams.KEEP_KEYINFO_UNSIGNED, Boolean.FALSE.toString())); // ********** FIN LECTURA PARAMETROS ADICIONALES // ************************************************* // *********************************************************************************************** URI uri = null; try { uri = extraParams.getProperty(XAdESExtraParams.URI) != null ? Util.createURI(extraParams.getProperty(XAdESExtraParams.URI)) : null; } catch (final Exception e) { logger.warning("Se ha pasado una URI invalida como referencia a los datos a firmar: " + e); } // Utils.checkIllegalParams(format, // extraParams.getProperty(XAdESExtraParams.MODE, // SignConstants.SIGN_MODE_IMPLICIT), uri, // precalculatedHashAlgorithm, true); // Propiedades del documento XML original Map originalXMLProperties = new Hashtable<>(); // Factoria XML DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); // Elemento de datos Element dataElement; // Documento final de firma Document docSignature = null; String contentId = XAdESSigner.DETACHED_CONTENT_ELEMENT_NAME + "-" + UUID.randomUUID().toString(); String styleId = XAdESSigner.DETACHED_STYLE_ELEMENT_NAME + "-" + UUID.randomUUID().toString(); boolean avoidDetachedContentInclusion = false; // Elemento de estilo XmlStyle xmlStyle = new XmlStyle(); // Nodo donde insertar la firma Element signatureInsertionNode = null; try { // Obtenemos el objeto XML Document docum = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(data)); // Si no hay asignado un MimeType o es el por defecto // establecemos el de XML if (mimeType == null || XMLConstants.DEFAULT_MIMETYPE.equals(mimeType)) { mimeType = "text/xml"; } // Obtenemos las propiedades del documento original originalXMLProperties = XAdESUtil.getOriginalXMLProperties(docum, outputXmlEncoding); dataElement = docum.getDocumentElement(); } // Captura de error en caso de no ser un documento XML // ********************************************************** // ********* Contenido no XML ******************************* // ********************************************************** catch (final Exception e) { throw new InvalidXMLException("Las firmas XAdES Enveloped solo pueden realizarse sobre datos XML", e); } // ********************************************************** // ********* Fin contenido no XML *************************** // ********************************************************** // *************************************************** // *************************************************** // La URI de contenido a firmar puede ser el nodo especifico si asi se // indico o el // nodo de contenido completo String tmpUri = "#" + contentId; String tmpStyleUri = "#" + styleId; try { docSignature = dbf.newDocumentBuilder().newDocument(); docSignature.appendChild(docSignature.adoptNode(dataElement)); } catch (Exception e) { throw new RubricaException("Error al crear la firma en formato Enveloped" + ": " + e, e); } List referenceList = new ArrayList<>(); XMLSignatureFactory fac = Utils.getDOMFactory(); DigestMethod digestMethod; try { digestMethod = fac.newDigestMethod(digestMethodAlgorithm, null); } catch (Exception e) { throw new RubricaException("No se ha podido obtener un generador de huellas digitales para el algoritmo '" + digestMethodAlgorithm + "'", e); } String referenceId = "Reference-" + UUID.randomUUID().toString(); List transformList = new ArrayList<>(); // Primero anadimos las transformaciones a medida Utils.addCustomTransforms(transformList, extraParams, XAdESSigner.XML_SIGNATURE_PREFIX); Transform canonicalizationTransform; if (canonicalizationAlgorithm != null) { try { canonicalizationTransform = fac.newTransform(canonicalizationAlgorithm, (TransformParameterSpec) null); } catch (Exception e1) { throw new RubricaException("No se ha posido crear el canonizador para el algoritmo indicado (" + canonicalizationAlgorithm + "): " + e1, e1); } } else { canonicalizationTransform = null; } if (canonicalizationTransform != null) { try { // Transformada para la canonicalizacion inclusiva transformList.add(canonicalizationTransform); } catch (Exception e) { throw new RubricaException("No se puede encontrar el algoritmo de canonicalizacion: " + e, e); } } // Crea una referencia indicando que se trata de una firma enveloped try { // Transformacion enveloped. // La enveloped siempre la primera, para que no se quede sin // nodos Signature por haber ejecutado antes otra transformacion transformList.add(fac.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null)); // Establecemos que es lo que se firma // 1.- Si se especifico un nodo, se firma ese nodo // 2.- Si el raiz tiene Id, se firma ese Id // 3.- Se firma todo el XML con "" // Tiene la raiz un Id? String ident = docSignature.getDocumentElement().getAttribute(ID_IDENTIFIER); if (ident != null && !ident.isEmpty()) { nodeToSign = ident; } // Salvo que sea una factura electronica o que se indique lo // contrario // se agrega una transformacion XPATH para eliminar el resto de // firmas del documento en las firmas Enveloped if (!facturaeSign && !avoidXpathExtraTransformsOnEnveloped) { transformList.add(fac.newTransform(Transform.XPATH, new XPathFilterParameterSpec( "not(ancestor-or-self::" + XAdESSigner.XML_SIGNATURE_PREFIX + ":Signature)", Collections.singletonMap(XAdESSigner.XML_SIGNATURE_PREFIX, XMLSignature.XMLNS)))); } // Crea la referencia referenceList.add(fac.newReference(nodeToSign != null ? "#" + nodeToSign : "", digestMethod, transformList, XMLConstants.OBJURI, referenceId)); } catch (Exception e) { throw new RubricaException("Error al generar la firma en formato enveloped: " + e, e); } // Nodo donde insertar la firma if (nodeToSign != null) { signatureInsertionNode = CustomUriDereferencer.getElementById(docSignature, nodeToSign); } // Instancia XADES_EPES XAdES_EPES xades = (XAdES_EPES) XAdES.newInstance(XAdES.EPES, // XAdES xadesNamespace, // XAdES NameSpace XAdESSigner.XADES_SIGNATURE_PREFIX, // XAdES Prefix XAdESSigner.XML_SIGNATURE_PREFIX, // XMLDSig Prefix digestMethodAlgorithm, // DigestMethod docSignature, // Document signatureInsertionNode != null ? // Nodo donde se inserta la // firma (como hijo), si no // se indica se usa la raiz signatureInsertionNode : docSignature.getDocumentElement()); // SigningCertificate xades.setSigningCertificate((X509Certificate) certChain[0]); XAdESCommonMetadataUtil.addCommonMetadata(xades, extraParams); // DataObjectFormat String oid = extraParams.getProperty(XAdESExtraParams.CONTENT_TYPE_OID); if (oid == null && mimeType != null) { try { oid = MimeHelper.transformMimeTypeToOid(mimeType); } catch (final Exception e) { logger.warning("Error en la obtencion del OID del tipo de datos a partir del MimeType: " + e); } // Si no se reconoce el MimeType se habra establecido el por // defecto. Evitamos este comportamiento if (!MimeHelper.DEFAULT_MIMETYPE.equals(mimeType) && MimeHelper.DEFAULT_CONTENT_OID_DATA.equals(oid)) { oid = null; } } ObjectIdentifierImpl objectIdentifier = oid != null ? new ObjectIdentifierImpl("OIDAsURN", (oid.startsWith("urn:oid:") ? "" : "urn:oid:") + oid, null, new ArrayList(0)) : null; ArrayList objectFormats = new ArrayList<>(); DataObjectFormat objectFormat = new DataObjectFormatImpl(null, objectIdentifier, mimeType != null ? mimeType : XMLConstants.DEFAULT_MIMETYPE, encoding, "#" + referenceId); objectFormats.add(objectFormat); xades.setDataObjectFormats(objectFormats); // CommitmentTypeIndications: // - // http://www.w3.org/TR/XAdES/#Syntax_for_XAdES_The_CommitmentTypeIndication_element // - http://uri.etsi.org/01903/v1.2.2/ts_101903v010202p.pdf List ctis = XAdESUtil.parseCommitmentTypeIndications(extraParams, referenceId); if (ctis != null && ctis.size() > 0) { xades.setCommitmentTypeIndications(ctis); } RubricaXMLAdvancedSignature xmlSignature = XAdESUtil.getXmlAdvancedSignature(xades, signedPropertiesTypeUrl, digestMethodAlgorithm, canonicalizationAlgorithm != null ? canonicalizationAlgorithm : CanonicalizationMethod.INCLUSIVE); // Genera la firma try { xmlSignature.sign(Arrays.asList(certChain), pk, algoUri, referenceList, "Signature-" + UUID.randomUUID().toString(), addKeyInfoKeyValue, addKeyInfoKeyName, addKeyInfoX509IssuerSerial, keepKeyInfoUnsigned); } catch (NoSuchAlgorithmException e) { throw new UnsupportedOperationException( "Los formatos de firma XML no soportan el algoritmo de firma '" + algorithm + "':" + e, e); } catch (final Exception e) { throw new RubricaException("Error al generar la firma XAdES: " + e, e); } // Si no es enveloped quito los valores del estilo para que no se // inserte la // cabecera de hoja de estilo return Utils.writeXML(docSignature.getDocumentElement(), originalXMLProperties); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy