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

io.rubrica.xml.Utils Maven / Gradle / Ivy

There is a newer version: 0.1.8
Show 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.xml;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URI;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;

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.XPathFilter2ParameterSpec;
import javax.xml.crypto.dsig.spec.XPathFilterParameterSpec;
import javax.xml.crypto.dsig.spec.XPathType;
import javax.xml.crypto.dsig.spec.XPathType.Filter;
import javax.xml.transform.OutputKeys;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;

import io.rubrica.core.Util;
import io.rubrica.sign.SignConstants;
import io.rubrica.sign.SignInfo;
import io.rubrica.sign.XMLConstants;

/**
 * Utilidades para las firmas XML.
 */
public final class Utils {

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

	/**
	 * Añade transformaciones segú la sintaxis de parámetros
	 * adicionales en fichero de propiedades del Cliente @firma a una lista
	 * pre-existente.
	 * 
	 * @param transforms
	 *            Lista a la que añadir las transformaciones
	 * @param xParams
	 *            Información sobre las transformaciones a añadir
	 * @param xmlSignaturePrefix
	 *            Prefijo XMLDSig
	 */
	public static void addCustomTransforms(final List transforms, final Properties xParams,
			final String xmlSignaturePrefix) {

		final List transformList = transforms != null ? transforms : new ArrayList();
		final Properties extraParams = xParams != null ? xParams : new Properties();

		// primero compruebo si hay transformaciones a medida
		final int numTransforms = Integer.parseInt(extraParams.getProperty("xmlTransforms", "0"));
		String transformType;
		String transformBody;
		String transformSubtype;
		XPathType.Filter xPath2TransformFilter;
		TransformParameterSpec transformParam;

		for (int i = 0; i < numTransforms; i++) {
			transformType = extraParams.getProperty("xmlTransform" + Integer.toString(i) + "Type");
			transformBody = extraParams.getProperty("xmlTransform" + Integer.toString(i) + "Body");

			if (Transform.XPATH.equals(transformType) && transformBody != null) {
				try {
					transformParam = new XPathFilterParameterSpec(transformBody,
							Collections.singletonMap(xmlSignaturePrefix, XMLSignature.XMLNS));
				} catch (final Exception e) {
					logger.warning(
							"No se han podido crear los parametros para una transformacion XPATH, se omitira: " + e);
					continue;
				}
			} else if (Transform.XPATH2.equals(transformType) && transformBody != null) {
				transformSubtype = extraParams.getProperty("xmlTransform" + Integer.toString(i) + "Subtype");
				if ("subtract".equals(transformSubtype)) {
					xPath2TransformFilter = Filter.SUBTRACT;
				} else if ("intersect".equals(transformSubtype)) {
					xPath2TransformFilter = Filter.INTERSECT;
				} else if ("union".equals(transformSubtype)) {
					xPath2TransformFilter = Filter.UNION;
				} else {
					logger.warning("Se ha solicitado aplicar una transformacion XPATH2 de un tipo no soportado: "
							+ transformSubtype);
					continue;
				}
				try {
					transformParam = new XPathFilter2ParameterSpec(
							Collections.singletonList(new XPathType(transformBody, xPath2TransformFilter)));
				} catch (final Exception e) {
					logger.warning(
							"No se han podido crear los parametros para una transformacion XPATH2, se omitira: " + e);
					continue;
				}
			} else if (Transform.BASE64.equals(transformType)) {
				// La transformacion Base64 no necesita parametros
				transformParam = null;
			} else if (Transform.ENVELOPED.equals(transformType)) {
				// La transformacion Enveloped no necesita parametros
				transformParam = null;
			} else {
				logger.warning("Tipo de transformacion no soportada: " + transformType);
				continue;
			}

			// Llegados a este punto tenemos ya la transformacion, asi que la
			// anadimos
			try {
				transformList.add(Utils.getDOMFactory().newTransform(transformType, transformParam));
			} catch (final Exception e) {
				logger.warning("No se ha podido aplicar la transformacion '" + transformType + "': " + e);
			}
		}
	}

	/**
	 * Obtiene de un nodo de referencia de tipo Object la lista de
	 * transformaciones definidas. Si no tiene transfgormaciones definidas, devuelve
	 * {@code null}.
	 * 
	 * @param referenceNode
	 *            Nodo de tipo referencia.
	 * @param namespacePrefix
	 *            Prefijo del espacio de nombres de la firma (opcional).
	 * @return Listado de transformaciones.
	 * @throws InvalidAlgorithmParameterException
	 *             Cuando se encuentre un parámetro inválido para el
	 *             algoritmo de transformación.
	 * @throws NoSuchAlgorithmException
	 *             Cuando se encuentre un algoritmo de transformación no
	 *             soportado.
	 */
	public static List getObjectReferenceTransforms(final Node referenceNode, final String namespacePrefix)
			throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
		final ArrayList transformList = new ArrayList<>();

		// El nodo de referencia puede contener un nodo "Transforms" que a su
		// vez contiene
		// las distintas transformaciones
		final NodeList tmpNodeList = referenceNode.getChildNodes();

		for (int i = 0; i < tmpNodeList.getLength(); i++) {

			// Buscamos el nodo Transforms a secas o precedido por el namespace
			if ("Transforms".equals(tmpNodeList.item(i).getNodeName())
					|| (namespacePrefix + ":Transforms").equals(tmpNodeList.item(i).getNodeName())) {

				final NodeList transformsNodeList = tmpNodeList.item(i).getChildNodes();

				for (int j = 0; j < transformsNodeList.getLength(); j++) {

					transformList
							.add(Utils.getDOMFactory().newTransform(getTransformAlgorithm(transformsNodeList.item(j)),
									getTransformParameterSpec(transformsNodeList.item(j), namespacePrefix)));
				}

				// Si ya hemos encontrado el nodo "Transforms" dejamos de buscar
				break;
			}
		}

		return transformList;
	}

	/**
	 * Recupera el identificador del algoritmo de un nodo de transformación.
	 * Si no existe el atributo de algoritmo, se devuelve {@code null}.
	 * 
	 * @param transformNode
	 *            Nodo de transformación.
	 * @return Algoritmo de transformación.
	 */
	private static String getTransformAlgorithm(final Node transformNode) {
		if (transformNode == null) {
			return null;
		}
		final Node algorithmNode = transformNode.getAttributes().getNamedItem("Algorithm");
		if (algorithmNode != null) {
			return algorithmNode.getNodeValue();
		}
		return null;
	}

	/**
	 * Recupera los parámetros de una transformación. En el caso de
	 * las transformaciones XPATH y XPATH2, se devolveran los parámetros
	 * especificados y, en las transformacion Base64, Enveloped y de
	 * Canonicalización (que no reciben parámetros) se
	 * devolverá {@code null}, al igual que cuando no se reconozca el tipo de
	 * transformación.
	 * 
	 * @param transformNode
	 *            Nodo de transformación.
	 * @param namespacePrefix
	 *            Prefijo del espacio de nombres XML
	 * @return Parámetros de la transformación.
	 * @throws InvalidAlgorithmParameterException
	 *             Cuando no se especifiquen correctamente los parámnetros de
	 *             las transformaciones XPATH y XPATH2.
	 */
	private static TransformParameterSpec getTransformParameterSpec(final Node transformNode,
			final String namespacePrefix) throws InvalidAlgorithmParameterException {

		TransformParameterSpec params = null;
		final String algorithm = getTransformAlgorithm(transformNode);

		// Comprobamos que la transformacion sea de tipo XPATH o XPATH2, unicos
		// casos en los que
		// la transformacion recibe parametros
		if (algorithm != null && (Transform.XPATH.equals(algorithm) || Transform.XPATH2.equals(algorithm))) {

			// Si es una transformacion XPATH solo tenemos que recoger el cuerpo
			if (Transform.XPATH.equals(algorithm)) {

				final NodeList xpathTransforms = transformNode.getChildNodes();
				for (int i = 0; i < xpathTransforms.getLength(); i++) {
					final Node xpathTransformNode = xpathTransforms.item(i);

					// Probamos a encontrar un nodo XPath sin namespace y con el
					// namespace indicado
					if ("XPath".equals(xpathTransformNode.getNodeName())
							|| (namespacePrefix + ":XPath").equals(xpathTransformNode.getNodeName())) {
						if (namespacePrefix == null || namespacePrefix.isEmpty()) {
							params = new XPathFilterParameterSpec(xpathTransformNode.getTextContent());
						} else {
							params = new XPathFilterParameterSpec(xpathTransformNode.getTextContent(),
									Collections.singletonMap(namespacePrefix, XMLSignature.XMLNS));
						}

						break;
					}
				}

				if (params == null) {
					throw new InvalidAlgorithmParameterException(
							"No se ha indicado un cuerpo para una transformacion XPATH declarada");
				}
			}
			// Si la transformacion es XPATH2 debemos tomar el cuerpo y el
			// subtipo
			else if (Transform.XPATH2.equals(algorithm)) {

				NodeList xpathTransforms = transformNode.getChildNodes();
				for (int i = 0; i < xpathTransforms.getLength(); i++) {
					Node xpathTransformNode = xpathTransforms.item(i);
					if ("XPath".equals(xpathTransformNode.getNodeName())
							|| (namespacePrefix + ":XPath").equals(xpathTransformNode.getNodeName())) {

						Filter filter;
						Node filterNode = xpathTransformNode.getAttributes().getNamedItem("Filter");
						if (filterNode != null) {
							String filterName = filterNode.getNodeValue();
							if (filterName.equals("subtract")) {
								filter = Filter.SUBTRACT;
							} else if (filterName.equals("intersect")) {
								filter = Filter.INTERSECT;
							} else if (filterName.equals("union")) {
								filter = Filter.UNION;
							} else {
								throw new InvalidAlgorithmParameterException(
										"El subtipo '" + filterName + "' de la transformacion XPATH2 no es valido");
							}
						} else {
							throw new InvalidAlgorithmParameterException(
									"No se ha declarado un subtipo para la transformacion XPATH2");
						}

						params = new XPathFilter2ParameterSpec(
								Collections.singletonList(new XPathType(xpathTransformNode.getTextContent(), filter)));
						break;
					}
				}

				if (params == null) {
					throw new InvalidAlgorithmParameterException(
							"No se ha indicado un cuerpo para una transformacion XPATH2 declarada");
				}
			}
		}

		return params;
	}

	/**
	 * Comprueba si hay alguna incorrección en los parámetros
	 * principales de firma.
	 * 
	 * @param format
	 *            Formato de firma
	 * @param mode
	 *            Modo de firma
	 * @param uri
	 *            URI del objeto a firmar
	 * @param externallyDetachedHashAlgorithm
	 *            Algoritmo de huella digital en el caso de estar esta pre-calculada
	 * @param xades
	 *            true si la firma es XAdES, false si es
	 *            XMLDSig
	 */
	public static void checkIllegalParams(final String format, final String mode, final URI uri,
			final String externallyDetachedHashAlgorithm, final boolean xades) {
		if (!mode.equals(SignConstants.SIGN_MODE_IMPLICIT) && !mode.equals(SignConstants.SIGN_MODE_EXPLICIT)) {
			throw new UnsupportedOperationException("El modo de firma '" + mode + "' no esta soportado");
		}

		if (xades) { // XAdES
			if (format.equals(SignConstants.SIGN_FORMAT_XADES_ENVELOPED)
					&& mode.equals(SignConstants.SIGN_MODE_EXPLICIT)) {
				throw new UnsupportedOperationException(
						"El formato Enveloped es incompatible con el modo de firma explicito");
			}
			if (!format.equals(SignConstants.SIGN_FORMAT_XADES_DETACHED)
					&& !format.equals(SignConstants.SIGN_FORMAT_XADES_ENVELOPED)
					&& !format.equals(SignConstants.SIGN_FORMAT_XADES_ENVELOPING)
					&& !format.equals(SignConstants.SIGN_FORMAT_XADES_EXTERNALLY_DETACHED)) {
				throw new UnsupportedOperationException("El formato de firma '" + format + "' no esta soportado");
			}
		} else { // XMLDSig

			if (format.equals(SignConstants.SIGN_FORMAT_XMLDSIG_ENVELOPED)
					&& mode.equals(SignConstants.SIGN_MODE_EXPLICIT)) {
				throw new UnsupportedOperationException(
						"No se puede realizar una firma enveloped sobre un contenido explicito");
			}
			if (format.equals(SignConstants.SIGN_FORMAT_XMLDSIG_EXTERNALLY_DETACHED)
					&& mode.equals(SignConstants.SIGN_MODE_IMPLICIT)) {
				throw new UnsupportedOperationException(
						"No se puede realizar una firma XML Externally Detached con contenido Implicito");
			}
			if (format.equals(SignConstants.SIGN_FORMAT_XMLDSIG_EXTERNALLY_DETACHED) && uri == null
					&& externallyDetachedHashAlgorithm == null) {
				throw new UnsupportedOperationException(
						"La firma XML Externally Detached necesita un Message Digest precalculado o una URI accesible");
			}

			// Formatos y modos soportados
			if (!format.equals(SignConstants.SIGN_FORMAT_XMLDSIG_DETACHED)
					&& !format.equals(SignConstants.SIGN_FORMAT_XMLDSIG_ENVELOPED)
					&& !format.equals(SignConstants.SIGN_FORMAT_XMLDSIG_ENVELOPING)
					&& !format.equals(SignConstants.SIGN_FORMAT_XMLDSIG_EXTERNALLY_DETACHED)) {
				throw new UnsupportedOperationException("El formato de firma '" + format + "' no esta soportado");
			}
		}
	}

	/**
	 * Intenta determinar la URL de declaración de espacio de nombres de
	 * XAdES de una firma XAdES.
	 * 
	 * @param el
	 *            Firma XAdES
	 * @return URL de la declaración del espacio de nombres
	 */
	public static String guessXAdESNamespaceURL(Node el) {
		String latest = "\"http://uri.etsi.org/01903#\"";
		String xades122 = "\"http://uri.etsi.org/01903/v1.2.2#\"";
		String xades132 = "\"http://uri.etsi.org/01903/v1.3.2#\"";
		String xades141 = "\"http://uri.etsi.org/01903/v1.4.1#\"";

		String signatureText = new String(writeXML(el, null));

		int numLatest = countSubstring(signatureText, latest);
		int numXades122 = countSubstring(signatureText, xades122);
		int numXades132 = countSubstring(signatureText, xades132);
		int numXades141 = countSubstring(signatureText, xades141);

		// Prioridad: xades132 > latest > xades141 > xades122
		if (numXades132 >= numLatest && numXades132 >= numXades141 && numXades132 >= numXades122) {
			return xades132.replace("\"", "");
		}
		if (numLatest >= numXades132 && numLatest >= numXades141 && numLatest >= numXades122) {
			return latest.replace("\"", "");
		}
		if (numXades141 >= numLatest && numXades141 >= numXades132 && numXades141 >= numXades122) {
			return xades141.replace("\"", "");
		}
		if (numXades122 >= numXades132 && numXades122 >= numLatest && numXades122 >= numXades141) {
			return xades122.replace("\"", "");
		}

		return xades132.replace("\"", "");
	}

	/**
	 * Intenta determinar el prefijo del espacio de nombres de XAdES.
	 * 
	 * @param el
	 *            Firma XAdES
	 * @return Prefijo del espacio de nombres
	 */
	public static String guessXAdESNamespacePrefix(final Element el) {
		final String signatureText = new String(writeXML(el, null));
		if (signatureText.contains("xmlns:etsi=\"http://uri.etsi.org/")) {
			return "etsi";
		}
		return "xades";
	}

	/**
	 * Intenta determinar el prefijo del espacio de nombres de la firma XMLDSig.
	 * 
	 * @param el
	 *            Firma XMLDSig
	 * @return Prefijo del espacio de nombres
	 */
	public static String guessXmlDSigNamespacePrefix(final Element el) {

		final String signatureText = new String(Utils.writeXML(el, null));

		final int numEmpty = Utils.countSubstring(signatureText, " "" > dsig > dsig11
		if (numDs >= numEmpty && numDs >= numDsig && numDs >= numDsig11) {
			return "ds";
		}
		if (numEmpty >= numDs && numEmpty >= numDsig && numEmpty >= numDsig11) {
			return "";
		}
		if (numDsig >= numEmpty && numDsig >= numDs && numDsig >= numDsig11) {
			return "dsig";
		}
		if (numDsig11 >= numEmpty && numDsig11 >= numDsig && numDsig11 >= numDs) {
			return "dsig11";
		}
		return "ds";
	}

	/**
	 * Cuenta las repeticiones de una subcadena dentro de una cadena. Las subcadenas
	 * no pueden estar acopladas.
	 * 
	 * @param text
	 *            Texto en el que realizar la búsqueda.
	 * @param substring
	 *            Subcadena que deseamos buscar.
	 * @return Número de coincidencias.
	 */
	public static int countSubstring(final String text, final String substring) {
		int count = 0;
		for (int i = 0; i <= text.length() - substring.length(); i++) {
			if (substring.charAt(0) == text.charAt(i) && substring.equals(text.substring(i, i + substring.length()))) {
				count++;
				i += substring.length() - 1;
			}
		}
		return count;
	}

	/**
	 * Escribe un XML como texto.
	 * 
	 * @param node
	 *            Nodo XML que queremos pasar a texto
	 * @param props
	 *            Propiedades del XML (version, encoding,
	 *            standalone)
	 * @param styleHref
	 *            Referencia (enlace) a la hoja de estilo del XML (puede ser nulo)
	 * @param styleType
	 *            Tipo de la hoja de estilo del XML (puede ser nulo)
	 * @return Cadena de texto con el XML en forma de array de octetos
	 */
	public static byte[] writeXML(Node node, Map props) {

		Map xmlProps = props != null ? props : new Hashtable(0);

		// La codificacion por defecto sera UTF-8
		String xmlEncoding = xmlProps.containsKey(OutputKeys.ENCODING) ? xmlProps.get(OutputKeys.ENCODING) : "UTF-8";

		// Primero creamos un writer
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		Writer writer = null;
		try {
			writer = new OutputStreamWriter(baos, xmlEncoding);
		} catch (UnsupportedEncodingException e) {
			logger.warning("La codificacion '" + xmlEncoding + "' no es valida, se usara la por defecto: " + e);
			writer = new OutputStreamWriter(baos);
		}

		// Ahora escribimos el XML usando XALAN
		writeXMLwithXALAN(writer, node, xmlEncoding);

		// Si no se trata de un XML completo y bien formado, devolvemos ya el
		// resultado
		if (!FileUtils.isXML(baos.toByteArray())) {
			return baos.toByteArray();
		}

		// Si se trata de un XML completo, insertamos la cabecera de hoja de
		// estilo
		try {
			return new String(baos.toByteArray(), xmlEncoding).getBytes(xmlEncoding);
		} catch (Exception e) {
			logger.warning(
					"La codificacion '" + xmlEncoding + "' no es valida, se usara la por defecto del sistema: " + e);
			return new String(baos.toByteArray()).getBytes();
		}
	}

	private static void writeXMLwithXALAN(Writer writer, Node node, String xmlEncoding) {
		DOMImplementationLS domImplLS;
		Document doc = node.getOwnerDocument();
		if (doc.getFeature("Core", "3.0") != null && doc.getFeature("LS", "3.0") != null) {
			domImplLS = (DOMImplementationLS) doc.getImplementation().getFeature("LS", "3.0");
		} else {
			throw new RuntimeException("La implementacion DOM cargada no permite la serializacion");
		}

		LSOutput output = domImplLS.createLSOutput();
		output.setCharacterStream(writer);
		if (xmlEncoding != null) {
			output.setEncoding(xmlEncoding);
		}

		LSSerializer serializer = domImplLS.createLSSerializer();
		serializer.getDomConfig().setParameter("namespaces", Boolean.FALSE);

		serializer.write(node, output);
	}

	/**
	 * Genera un objeto descriptor de firma.
	 * 
	 * @param namespace
	 *            Espacio de nombres utilizado para la recuperación de
	 *            atributos XAdES.
	 * @param signature
	 *            Nodo de firma.
	 * @return Objeto descriptor de firma.
	 */
	public static SignInfo getSimpleSignInfoNode(String namespace, Element signature) {
		// Recupera la fecha de firma
		Date signingTime = null;
		if (namespace != null) {
			try {
				signingTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
						.parse(((Element) signature.getElementsByTagNameNS(namespace, "SigningTime").item(0))
								.getTextContent());
			} catch (Exception e) {
				logger.warning("No se ha podido recuperar la fecha de firma: " + e);
			}
		}

		List certChain = new ArrayList<>();
		NodeList signatureNodes = signature.getElementsByTagNameNS(XMLConstants.DSIGNNS, "X509Certificate");
		for (int i = 0; i < signatureNodes.getLength(); i++) {
			certChain.add(Utils.getCertificate(signatureNodes.item(i)));
		}

		SignInfo ssi = new SignInfo(certChain.toArray(new X509Certificate[certChain.size()]), signingTime);
		ssi.setSignAlgorithm(
				((Element) signature.getElementsByTagNameNS(XMLConstants.DSIGNNS, "SignatureMethod").item(0))
						.getAttribute("Algorithm"));

		byte[] pkcs1;
		try {
			NodeList nodeList = signature.getElementsByTagNameNS(XMLConstants.DSIGNNS, "SignatureValue");
			Node node = nodeList.item(0);
			String base64 = node.getTextContent().trim().replace("\r", "").replace("\n", "").replace(" ", "")
					.replace("\t", "");
			pkcs1 = Base64.getDecoder().decode(base64);
		} catch (Exception e) {
			logger.warning("No se pudo extraer el PKCS#1 de una firma: " + e);
			pkcs1 = null;
		}

		ssi.setPkcs1(pkcs1);

		return ssi;
	}

	/**
	 * Obtiene el CN del certificado de una firma.
	 * 
	 * @param signature
	 *            Nodo de firma.
	 * @return CN del certificado de firma.
	 */
	public static String getStringInfoNode(final Element signature) {
		return Util.getCN(Utils
				.getCertificate(signature.getElementsByTagNameNS(XMLConstants.DSIGNNS, "X509Certificate").item(0)));
	}

	/**
	 * Genera un certificado X.509 a partir de un nodo de certificado de firma.
	 * 
	 * @param certificateNode
	 *            Nodo "X509Certificate" de la firma.
	 * @return Certificado de firma.
	 */
	public static X509Certificate getCertificate(Node certificateNode) {
		return createCert(certificateNode.getTextContent().trim().replace("\r", "").replace("\n", "").replace(" ", "")
				.replace("\t", ""));
	}

	/**
	 * Recupera el identificador (id) de la firma sobre la que se ha realizado una
	 * contrafirma. Si no se encuentra la firma a la que se referencia se devuelve
	 * cadena vacía.
	 * 
	 * @param signature
	 *            Nodo de la contrafirma.
	 * @param signatureValues
	 *            Listado con todos los SignatureValue del documento de firma.
	 * @return Identificador de la firma (Signature) referenciada.
	 */
	public static String getCounterSignerReferenceId(final Element signature, final NodeList signatureValues) {
		// Tomamos la URI de la primera referencia (la del objeto firmado),
		// evitando el primer caracter de la URI que sera la almohadilla (#)
		final String uri = ((Element) signature.getElementsByTagNameNS(XMLConstants.DSIGNNS, "Reference").item(0)) //$NON-NLS-1$
				.getAttribute("URI").substring(1); //$NON-NLS-1$
		String signatureId = ""; //$NON-NLS-1$
		for (int j = 0; j < signatureValues.getLength(); j++) {
			final Element signatureValue = (Element) signatureValues.item(j);
			if (signatureValue.getAttribute("Id").equals(uri)) { //$NON-NLS-1$
				signatureId = ((Element) signatureValue.getParentNode()).getAttribute("Id"); //$NON-NLS-1$
				break;
			}
		}
		return signatureId;
	}

	/**
	 * Crea un X509Certificate a partir de un certificado en Base64.
	 * 
	 * @param b64Cert
	 *            Certificado en Base64. No debe incluir Bag Attributes
	 * @return Certificado X509 o null si no se pudo crear
	 */
	public static X509Certificate createCert(String b64Cert) {
		if (b64Cert == null || b64Cert.isEmpty()) {
			logger.severe("Se ha proporcionado una cadena nula o vacia, se devolvera null");
			return null;
		}
		X509Certificate cert;
		try (InputStream isCert = new ByteArrayInputStream(Base64.getDecoder().decode(b64Cert));) {
			cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(isCert);
			try {
				isCert.close();
			} catch (Exception e) {
				logger.warning("Error cerrando el flujo de lectura del certificado: " + e);
			}
		} catch (Exception e) {
			logger.severe("No se pudo decodificar el certificado en Base64, se devolvera null: " + e);
			return null;
		}
		return cert;
	}

	/**
	 * Recupera la factoría de firmas XML preferente.
	 * 
	 * @return Factoría de firmas XML
	 */
	public static XMLSignatureFactory getDOMFactory() {
		return XMLSignatureFactory.getInstance("DOM");
	}

	private static final String XMLDSIG = "XMLDSig";

	/**
	 * Instala el proveedor de firmas XMLDSig para el entorno de ejecución de
	 * Java en uso.
	 * 
	 * @param forceApacheProvider
	 *            Indica si debe forzarse al uso de uno de los proveedores de
	 *            Apache.
	 */
	public static void installXmlDSigProvider() {
		Security.getProvider(XMLDSIG);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy