Java tutorial
/* Copyright (C) 2007 Flix Garca Borrego (borrego at gmail.com) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package org.viafirma.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.c14n.Canonicalizer; import org.apache.xml.security.c14n.InvalidCanonicalizerException; import org.apache.xml.security.exceptions.Base64DecodingException; import org.apache.xml.security.exceptions.XMLSecurityException; import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.keys.keyresolver.KeyResolverException; import org.apache.xml.security.signature.ObjectContainer; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.signature.XMLSignatureException; import org.apache.xml.security.transforms.Transforms; import org.apache.xml.security.utils.Constants; import org.apache.xml.security.utils.XMLUtils; import org.apache.xpath.XPathAPI; import org.viafirma.cliente.exception.InternalException; import org.viafirma.cliente.firma.TypeFile; import org.viafirma.cliente.firma.TypeFormatSign; import org.viafirma.excepciones.CodigoError; import org.viafirma.excepciones.ExcepcionCertificadoNoEncontrado; import org.viafirma.excepciones.ExcepcionErrorInterno; import org.viafirma.excepciones.ExcepcionManejoCertificado; import org.viafirma.nucleo.validacion.KeyStoreLoader; import org.viafirma.vo.Documento; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * Contiene los mtodos de utilidad para manejar XmlSignatures. * * Recupera el certificado desde un xmlSign codificado en Base64. Obtiene los * datos contenidos dentro de un XmlSignature de tipo Enveloped. * * * @author Felix Garcia Borrego (borrego at gmail.com) * @author Alexis Castilla Armero (Pencerval at gmail.com) */ public class XmlSignUtil { private Log log = LogFactory.getLog(XmlSignUtil.class); private static String BaseURI = "signature.xml"; private static XmlSignUtil singleton; // private XPathFactory xPathFactory; public static XmlSignUtil getInstance() { if (singleton == null) { singleton = new XmlSignUtil(); } return singleton; } /** * */ private XmlSignUtil() { // singleton. // xPathFactory = XPathFactory.newInstance(); } /** * Recupera el certificado parseando un String (Base64) Recupera el * XmlSignature y delega en getCertificado(XMLSignature signature) * * @param xmldoc_b64 * @return Certificado del usuario * @throws ExcepcionErrorInterno * @throws ExcepcionManejoCertificado * @throws ExcepcionCertificadoNoEncontrado * @throws ExcepcionManejoCertificado * Error al procesar el certificado dentro del xmls * @throws ExcepcionCertificadoNoEncontrado * No hay certificado */ public List<X509Certificate> parseCertificado(String xmldoc_b64) throws ExcepcionErrorInterno, ExcepcionCertificadoNoEncontrado, ExcepcionManejoCertificado { List<X509Certificate> listCertificados = new LinkedList<X509Certificate>(); Document documento = getDocumentFromBase64(xmldoc_b64); // recupera los XMLSignature almacenados en el documento List<XMLSignature> signatureList = getXMLSignatureFormDocument(documento); // Para cada elemento recuperamos su certificado asociado. for (XMLSignature signature : signatureList) { listCertificados.add(getX509(signature)); } return listCertificados; } /** * Parsea el XML y recupera el xmlsignature 1.- Decodifica el string en * base64 para obtener el xml. 2.- Parsea el xml para obtener un DOM ( NOTA: * este punto se puede obtimizar en el futuro para que utilize SAX. 3.- * Retorna el objeto XMLSignature que contiene el restultado de la firma( o * autenticacin) * * @param xmldoc_b64 * xml en Base64 * @return Documento que contiene el resultado de la firma o autenticacin. * @throws ExcepcionManejoCertificado * No se puede recuperar el certificado */ public Document getDocumentFromBase64(String xmldoc_b64) throws ExcepcionErrorInterno { // Obtenemos el String con el XML desde el String base 64 String xmlDoc; try { xmlDoc = new String(org.apache.xml.security.utils.Base64.decode(xmldoc_b64), "UTF-8"); } catch (UnsupportedEncodingException e) { // no se ha podido decodificar la base64. el usuario no esta // enviando un certificdo valido throw new ExcepcionErrorInterno("Conjunto de caracteres no soportado", e); } catch (Base64DecodingException e) { // no se ha podido decodificar la base64.el usuario no esta enviando // un certificado valio throw new ExcepcionErrorInterno("Error al decodificar Base64", e); } if (log.isDebugEnabled()) { log.debug("XMLSign:" + xmlDoc); } return parseDocument(xmlDoc); } /** * Parsea un String que contiene un XmlSignature retornando la estructura * XMLSignature. * * @param xmlDoc * @return * @throws ExcepcionManejoCertificado */ public Document parseDocument(String xmlDoc) throws ExcepcionErrorInterno { StringReader readerXml = new StringReader(xmlDoc); return parseDocument(readerXml); } /** * Parsea un String que contiene un XmlSignature retornando la estructura * XMLSignature. * * @param xmlDoc * @return * @throws ExcepcionManejoCertificado */ public Document parseDocument(InputStream input) throws ExcepcionErrorInterno { InputStreamReader readerXml = new InputStreamReader(input); return parseDocument(readerXml); } /** * Parsea un String que contiene un XmlSignature retornando la estructura * XMLSignature. * * @param xmlDoc * @return * @throws ExcepcionManejoCertificado */ public Document parseDocument(byte[] input) throws ExcepcionErrorInterno { InputStreamReader readerXml = new InputStreamReader(new ByteArrayInputStream(input)); return parseDocument(readerXml); } /** * Parsea el xml asociado * * @param xmldoc_b64 * @return * @throws ExcepcionManejoCertificado */ public Document parseDocument(Reader readerXml) throws ExcepcionErrorInterno { try { // parser InputSource ioXml = new InputSource(readerXml); // InputStream in=ioXml.getByteStream(); javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE); dbf.setValidating(false); javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder(); org.w3c.dom.Document doc = db.parse(ioXml); return doc; } catch (ParserConfigurationException e) { throw new ExcepcionErrorInterno("ParserConfigurationException", e); } catch (SAXException e) { throw new ExcepcionErrorInterno("ParserConfigurationException", e); } catch (IOException e) { throw new ExcepcionErrorInterno("ParserConfigurationException", e); } finally { if (readerXml != null) { try { readerXml.close(); } catch (IOException e) { log.warn("No se puede cerrar el InputStream para el XmlSignature"); } } } } /* * Recupera todos los XMLSignature incorporados dentro del documento. */ public List<XMLSignature> getXMLSignatureFormDocument(Document documento) throws ExcepcionErrorInterno { try { // Aadimos al documento los Signatures que tenia anteriormente para // permitir la multifirma // recuperamos el elemento DOM Element nscontext = XMLUtils.createDSctx(documento, "ds", Constants.SignatureSpecNS); // Element nscontext = XMLUtils.createDSctx(doc, // "ds","http://www.w3.org/2000/09/xmldsig#"); NodeList nodeList = XPathAPI.selectNodeList(documento, "//ds:Signature", nscontext); // Si se contien List<XMLSignature> xmlSignatureList = new ArrayList<XMLSignature>(nodeList.getLength()); // Extrae los hijos que ds:signature for (int i = 0; i < nodeList.getLength(); i++) { Element node = (Element) nodeList.item(i); XMLSignature signature = buildXMLSignature(node); xmlSignatureList.add(signature); } return xmlSignatureList; } catch (TransformerException e) { throw new ExcepcionErrorInterno("TransformerException", e); } catch (XMLSignatureException e) { throw new ExcepcionErrorInterno("XMLSignatureException", e); } catch (XMLSecurityException e) { throw new ExcepcionErrorInterno("XMLSecurityException", e); } } /** * Crea un XML Signature desde un nodo. * Considerando que el nodo tiene estructura de xmlSignature * @param node * @return * @throws XMLSignatureException * @throws XMLSecurityException */ public XMLSignature buildXMLSignature(Element node) throws XMLSignatureException, XMLSecurityException { XMLSignature signature = new XMLSignature(node, "viafirmaXMLSignature"); signature.addResourceResolver(new OfflineResolver()); return signature; } /** * Comprueba si el documento xml indicado esta vlido. * * Para ser vlido debe tener todas sus Signatures validas. Si no tiene * signatures es vlida * * @param documento * @return */ public boolean checkSignatureFrom(Document xmlDocument, boolean permitirNoFirmados) { boolean isValid = true; try { // Recuperamos todas las signatures del documento: List<XMLSignature> listSignatures = getXMLSignatureFormDocument(xmlDocument); // Comprueba que todas las Signatures del documento son vlidas. for (XMLSignature signature : listSignatures) { isValid = isValid && isValid(signature); } if (listSignatures.size() == 0) { isValid = permitirNoFirmados; } } catch (ExcepcionErrorInterno e) { log.warn( "Problemas al comprobar la seguridad del documento. La firma no se realizo correctamente, el formato no es el esperado o el documento ha sido modificado.", e); isValid = false; } return isValid; } /** * Retorna el tipo de formato utilizado para la firma * * @param newId * @return * @throws ExcepcionErrorInterno * El formato no es reconocido */ public static TypeFormatSign getTypeFormatSign(String newId) throws ExcepcionErrorInterno { try { int codTipo = Integer.parseInt(newId.substring(1, 2)); // Equivale // a esto // id.charAt(1); return TypeFormatSign.getByCode(codTipo); } catch (InternalException e) { throw new ExcepcionErrorInterno(CodigoError.ERROR_XMLSIGN_FORMAT, e); } } /** * Retorna el Documento custodiado preparado para su transmisin. * * @param signature * @param identificador * @return * @throws ExcepcionErrorInterno */ public Documento getDocument(Document xmlDocument, String identificador) throws ExcepcionErrorInterno { // Retorna el document en funcin del tipo TypeFormatSign typeFormatSign = getTypeFormatSign(identificador); if (typeFormatSign.equals(TypeFormatSign.XMLSIG_ENVELOPING)) { return getDocumentFromContentObjectInXMLSIG_ENVELOPING(xmlDocument, identificador); } else if (typeFormatSign.equals(TypeFormatSign.XADES_EPES_ENVELOPED)) { // es un Formato xml return getDocumentFromContentObjectInXADES_EPES_ENVELOPED(xmlDocument, identificador); } else { // Tipo de dato no reconocido throw new ExcepcionErrorInterno(CodigoError.ERROR_XMLSIGN_FORMAT); } } /** * Retorna el documento XML custodiado. * * @param signature * @param identificador * @return */ private Documento getDocumentFromContentObjectInXADES_EPES_ENVELOPED(Document xmlDocument, String identificador) { throw new UnsupportedOperationException( "Esta operacin no esta soportada o implementada en la versin Viafirma GPL/Estandar"); } /** * Retorna el contenido del objeto contenido en el xmlSignature. Si hay mas * de un objeto retorna el ltimo. * * @param signature * @return * @throws ExcepcionManejoCertificado */ public Documento getDocumentFromContentObjectInXMLSIG_ENVELOPING(Document xmlDocument, String identificador) throws ExcepcionErrorInterno { try { // Recupero las signaturas almacenadas en el documento List<XMLSignature> signatureList = getXMLSignatureFormDocument(xmlDocument); // En este tipo de formato solo tenemos una firma XMLSignature signature = signatureList.get(0); // Por convencin el ultimo objeto es el PDF firmado. ObjectContainer object = signature.getObjectItem(signature.getObjectLength() - 1); // Suponemos que el encoding es Base64 String base64 = object.getTextFromTextChild(); // Recupero el tipo de dato String mimeType = object.getMimeType(); TypeFile typeFile = TypeFile.getFromMimeType(mimeType); byte[] datos = org.apache.xml.security.utils.Base64.decode(base64); Documento documento = new Documento(identificador, identificador + typeFile.getExtension(), datos, typeFile, TypeFormatSign.XMLSIG_ENVELOPING); return documento; } catch (Base64DecodingException e) { throw new ExcepcionErrorInterno(CodigoError.ERROR_CUSTODIA); } } /** * Recupera el XMLSignature desde un Xml en formato Base64. * * @param xmldoc_b64n * String en Base64 que contiene un XmlSign * @return Xml con la firma * @throws ExcepcionManejoCertificado * No se ha podido generar el XMLSign * @throws ExcepcionCertificadoNoEncontrado * No hay certificado */ public X509Certificate getX509(XMLSignature signature) throws ExcepcionCertificadoNoEncontrado, ExcepcionManejoCertificado { try { // intento recuperar la informacin del firmante. KeyInfo ki = signature.getKeyInfo(); if (ki != null) { if (!ki.containsX509Data()) { // No hay informacin del firmante System.out.println("Could find a X509Data element in the KeyInfo"); throw new ExcepcionCertificadoNoEncontrado(CodigoError.ERROR_XMLSIGN_SIN_CERTIFICADO); } else { // el XMLSign si contiene un certificado de usuario X509Certificate cert = signature.getKeyInfo().getX509Certificate(); // if(signature.checkSignatureValue(cert)){ // System.out.println("Certificado valido"); // }else{ // System.out.println("Certificado No valido"); // } if (cert != null) { return cert; } else { // no se ha encontrado un certificado vlido throw new ExcepcionCertificadoNoEncontrado(CodigoError.ERROR_XMLSIGN_SIN_CERTIFICADO); } } } else { throw new ExcepcionCertificadoNoEncontrado(CodigoError.ERROR_XMLSIGN_SIN_CERTIFICADO); } } catch (XMLSecurityException e) { throw new ExcepcionManejoCertificado("XMLSecurityException", e); } } // ********************************************************************** // Mtodos para la generacin de XML Signatures // ********************************************************************** /** * Genera un XMLSignature de tipo Enveloped firmado por el alias indicado. * * @parm documeto Documento a firmar * @param alias * Alias del certificado utilizado. * @throws ExcepcionErrorInterno * No se ha podido generar la firma del documento * @throws ExcepcionCertificadoNoEncontrado * No existe el alias */ public Document signDocument(Documento documento, String alias, String password) throws ExcepcionErrorInterno, ExcepcionCertificadoNoEncontrado { // Recuperamos el certificado que vamos a utilizar PrivateKey privateKey; try { if (alias == null) { throw new ExcepcionCertificadoNoEncontrado("Alias indicado incorrecto"); } privateKey = KeyStoreLoader.getPrivateKey(alias, password); if (privateKey == null) { throw new ExcepcionCertificadoNoEncontrado( "No existe una clave privada para el alias '" + alias + "'"); } log.info("Firmando el documento con el certificado " + alias); // Creamos el documento que se convertira en un XMLSignature javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance(); javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder(); org.w3c.dom.Document doc = db.newDocument(); // Preparamos la estructura para la firma XML Signature XMLSignature sig = new XMLSignature(doc, BaseURI, XMLSignature.ALGO_ID_SIGNATURE_RSA); doc.appendChild(sig.getElement()); // Aadimos los datos a firmar al documento String idAttachment = "attachment-0"; ObjectContainer obj2 = new ObjectContainer(doc); obj2.setEncoding("base64"); obj2.setId(idAttachment); obj2.setMimeType(documento.getTipo().getMimetype()); obj2.addBase64Text(documento.getDatos()); sig.appendObject(obj2); // Creamos el tansformador a XMlSignature Transforms transforms = new Transforms(doc); transforms.addTransform(Transforms.TRANSFORM_C14N_WITH_COMMENTS); sig.addDocument("#" + idAttachment, transforms, Constants.ALGO_ID_DIGEST_SHA1); // Aadimos la informacin del certificado usado X509Certificate certificado = KeyStoreLoader.getCertificate(alias); // Aadimos la informacin del firmante sig.addKeyInfo(certificado); sig.addKeyInfo(certificado.getPublicKey()); // Firmamos el documento sig.sign(privateKey); log.debug("Firma completada."); return sig.getDocument(); } catch (ExcepcionErrorInterno e) { throw e; } catch (ParserConfigurationException e) { throw new ExcepcionErrorInterno(CodigoError.ERROR_MANEJO_CERTIFICADO, "No se puede genera el XMLSignature.", e); } catch (XMLSecurityException e) { throw new ExcepcionErrorInterno(CodigoError.ERROR_MANEJO_CERTIFICADO, "No se puede genera el XMLSignature.", e); } } /** * Retorna el digest en array * * @param XMLSignature * @return boolean * * @author Alexis Castilla Armero (Pencerval at gmail.com) */ public String[] getDigest(XMLSignature signature, String identificador) { try { String[] typePlusValue = new String[2]; TypeFormatSign formatSign = getTypeFormatSign(identificador); if (TypeFormatSign.XMLSIG_ENVELOPING == formatSign) { /* * ej: <ds:SignedInfo> <ds:CanonicalizationMethod * Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" * ></ds:CanonicalizationMethod> <ds:SignatureMethod * Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" * ></ds:SignatureMethod> <ds:Reference URI="#ToBeSigned"> * <ds:DigestMethod * Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" * ></ds:DigestMethod> * <ds:DigestValue>PR1yAlUUyMinlz2dUHMTn5icNm8=</ds:DigestValue> * </ds:Reference> <ds:Reference * Type="http://www.w3.org/2000/09/xmldsig#Object" * URI="#attachment-0"> <ds:DigestMethod * Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" * ></ds:DigestMethod> * <ds:DigestValue>1Gq4NHx9jdoIHeoy4YB9omeLKM4=</ds:DigestValue> * </ds:Reference> </ds:SignedInfo> */ // Recuperamos el valor almacenado en el Element nscontext = XMLUtils.createDSctx(signature.getSignedInfo().getDocument(), "ds", Constants.SignatureSpecNS); typePlusValue[0] = XPathAPI .selectSingleNode(signature.getSignedInfo().getElement(), "//ds:SignedInfo[1]/ds:Reference/ds:DigestMethod/@Algorithm", nscontext) .getNodeValue(); // typePlusValue[0] = //signature.getSignedInfo().getElement().getChildNodes().item(7) // .getChildNodes().item(1).getAttributes().getNamedItem( // "Algorithm").getTextContent(); typePlusValue[0] = StringUtils.substringAfterLast(typePlusValue[0], "#"); // typePlusValue[1] = //signature.getSignedInfo().getElement().getChildNodes().item(7) // .getChildNodes().item(3).getTextContent(); typePlusValue[1] = XPathAPI.selectSingleNode(signature.getSignedInfo().getElement(), "//ds:SignedInfo[1]/ds:Reference/ds:DigestValue/text()", nscontext).getNodeValue(); return typePlusValue; } else if (TypeFormatSign.XADES_EPES_ENVELOPED == formatSign) { throw new UnsupportedOperationException( "Esta operacin no esta soportada o implementada en la versin Viafirma GPL/Estandar"); } else { throw new UnsupportedOperationException( "El metodo no esta implementado para el tipo de formato: " + formatSign); } } catch (Exception e) { log.fatal("Hay problemas al procesar la estructura XML, no parece tener el formato esperado. id:" + identificador, e); return null; } } /** * Recupera informacin sobre el hash del documento. * * Nota: Recupera el valor del hash que aparece en la primera referencia del * documento en caso de se un XADES. * ej:http://www.w3.org/2000/09/xmldsig#sha1 --> sha1 * * @param xmlSig * @return */ public String getDigestFormated(XMLSignature signature, String identificador) { String[] typePlusValue = getDigest(signature, identificador); if (typePlusValue != null) { String type = typePlusValue[0]; String value = typePlusValue[1]; return type + ": " + value; } return null; } /** * Canonizacin del xml * * @param datos * @return */ public byte[] canonizar(byte[] datos) { try { Canonicalizer c14n = Canonicalizer.getInstance(Transforms.TRANSFORM_C14N_OMIT_COMMENTS); // Canoniza el documento return c14n.canonicalize(datos); } catch (InvalidCanonicalizerException e) { log.warn("El XML no se puede canonizar.", e); } catch (CanonicalizationException e) { log.warn("El XML no se puede canonizar.", e); } catch (ParserConfigurationException e) { log.warn("El XML no se puede canonizar.", e); } catch (IOException e) { log.warn("El XML no se puede canonizar.", e); } catch (SAXException e) { log.warn("El XML no se puede canonizar.", e); } return null; } /** * Canonizacin del xml * * @param doc * @return */ public byte[] canonizar(Node doc) { try { Canonicalizer c14n = Canonicalizer.getInstance(Transforms.TRANSFORM_C14N_WITH_COMMENTS); // Canoniza el documento return c14n.canonicalizeSubtree(doc); } catch (InvalidCanonicalizerException e) { log.warn("El XML no se puede canonizar.", e); } catch (CanonicalizationException e) { log.warn("El XML no se puede canonizar.", e); } return null; } /** * Transforma un xml a un String * @param doc * @return */ public String toString(Node doc) { ByteArrayOutputStream out = new ByteArrayOutputStream(); XMLUtils.outputDOM(doc, out); return new String(out.toByteArray()); } /** * Valida un XML Signature. * Calcula el keyInfo de una signature y la comprueba * * @param XMLSignature * @return boolean * * @author Alexis Castilla Armero (Pencerval at gmail.com) */ public boolean isValid(XMLSignature signature) { try { X509Certificate cert = signature.getKeyInfo().getX509Certificate(); return signature.checkSignatureValue(cert); } catch (KeyResolverException e) { // TODO Auto-generated catch block log.warn("XML Signature invalido (Imposible resolver la clave)."); } catch (XMLSignatureException e) { // TODO Auto-generated catch block log.warn("XML Signature invalido.", e); } return false; } }