package sign;

import javax.xml.crypto.*;
import javax.xml.crypto.dsig.*;
import javax.xml.crypto.dom.*;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.*;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;


import java.security.*;
import java.security.interfaces.*;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import org.xml.sax.SAXException;
import org.xml.sax.InputSource;

public class ValidateXmlSig
{
  public static String cadenaContenido="<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:mat=\"http://impl.service.qrws.ce.siat.sat.gob.mx/siatbus/matce\">\n" + 
  "   <soapenv:Header/>\n" + 
  "   <soapenv:Body>\n" + 
  "      <mat:consultaEspDoda>\n" + 
  "         <datosUsuario>\n" + 
  "            <usuario>HERM8008118K1</usuario>\n" + 
  "            <password>p4ssw0rd</password>\n" + 
  "         </datosUsuario>\n" + 
  "         <datosDoda>\n" + 
  "            <!--Optional:-->\n" + 
  "            <datosFirma>\n" + 
  "               <cadenaOriginal>||170|25|7028|2016-06-22 17:36:57||</cadenaOriginal>\n" + 
  "               <certificado>1</certificado>\n" + 
  "               <firmaElectronica>ay+RWQ1KmQA9KKVqyawH848unFhRw+Lg3z02zOTsKluBm0910oyZJwKdPY/CXZjrvJqKzHeZc3/Z1DDZSV9JOBfYsmpo7ly3z4F9TqbeKNzi3F7Jx/Nvr61Ip107eK0L9TWQiV8hv5x0lE1qMwj4eO5XpiaornrbIOOL145iXio=</firmaElectronica>\n" + 
  "            </datosFirma>\n" + 
  "            <datosGenerales>\n" + 
  "               <numIntegracion>500203</numIntegracion>\n" + 
  "               <tipoMovimiento>5</tipoMovimiento>\n" + 
  "            </datosGenerales>\n" + 
  "         </datosDoda>\n" + 
  "      </mat:consultaEspDoda>\n" + 
  "   </soapenv:Body>\n" + 
  "<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\"><SignedInfo><CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments\"/><SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#dsa-sha1\"/><Reference URI=\"\"><Transforms><Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/></Transforms><DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><DigestValue>ON5eONe84+VA11MdIbFjMlIy3pM=</DigestValue></Reference></SignedInfo><SignatureValue>B6DbADuKjQ9SxP73ybatiMA3ZKpxi/0OgM4WEuEG5yVoG7tOWJcDYQ==</SignatureValue><KeyInfo><KeyValue><DSAKeyValue><P>/X9TgR11EilS30qcLuzk5/YRt1I870QAwx4/gLZRJmlFXUAiUftZPY1Y+r/F9bow9subVWzXgTuA\n" + 
  "HTRv8mZgt2uZUKWkn5/oBHsQIsJPu6nX/rfGG/g7V+fGqKYVDwT7g/bTxR7DAjVUE1oWkTL2dfOu\n" + 
  "K2HXKu/yIgMZndFIAcc=</P><Q>l2BQjxUjC8yykrmCouuEC/BYHPU=</Q><G>9+GghdabPd7LvKtcNrhXuXmUr7v6OuqC+VdMCz0HgmdRWVeOutRZT+ZxBxCBgLRJFnEj6EwoFhO3\n" + 
  "zwkyjMim4TwWeotUfI0o4KOuHiuzpnWRbqN/C/ohNWLx+2J6ASQ7zKTxvqhRkImog9/hWuWfBpKL\n" + 
  "Zl6Ae1UlZAFMO/7PSSo=</G><Y>JBSU45UwrJtmRF4GJOpfG+6GfYFrw1iKmjEL02eZ8afG1RWadD8Sq+mzNKiBWe7mxKa8YDIL2J+m\n" + 
  "T8fqSiGddDlIk9Al1N3IFlA1V2aPucAEiJi+AxPSGqFxDulmBW8vUI6OESL/dsglXbI9uBvoTjIr\n" + 
  "UZbh5yjcHX3alPz0008=</Y></DSAKeyValue></KeyValue></KeyInfo></Signature>" +
  "</soapenv:Envelope>\n";
  
  
  public static void main(String[] args)
  {
    
    if(args.length<1)
    {
      System.out.println("Proporcione el nombre del archivo XML a verificar.");
      return;
    }
    
    try
    {
      DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
      dbf.setNamespaceAware(true);
      
      Document doc=dbf.newDocumentBuilder().parse(new FileInputStream(args[0]));
      
      /*
      //StringReader sr=new StringReader(cadenaContenido);
      InputSource inputSource=new InputSource(sr);
      Document doc=dbf.newDocumentBuilder().parse(inputSource);
      */
      
      // Obtener el nodo de la firma.
      NodeList nl=doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
      
      if(nl.getLength()==0)
      {
        System.out.println("No se pudo encontrar el nodo de la lista.");
        return;
      }
      
      
      String providerName=System.getProperty("jsr105Provider", "org.jcp.xml.dsig.internal.dom.XMLDSigRI");
      XMLSignatureFactory fac=XMLSignatureFactory.getInstance("DOM", (Provider)Class.forName(providerName).newInstance());
      
      for(int i=0;i<nl.getLength();i++)
      {
        DOMValidateContext valContext=new DOMValidateContext(new KeyValueSelector(), nl.item(i));
        
        valContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
        
        XMLSignature signature=fac.unmarshalXMLSignature(valContext);
        
        // Verificando la firma.
        boolean coreValidity=signature.validate(valContext);
        
        if(coreValidity==false)
        {
          System.out.println(String.format("La firma %s falló la validación central: ", i));
        
        
          boolean sv=signature.getSignatureValue().validate(valContext);
        
          System.out.println(String.format("El estatus de la  firma %s es: %s", i, sv));
        
          Iterator it=signature.getSignedInfo().getReferences().iterator();
        
          for(int j=0;it.hasNext();j++)
          {
            Reference ref=(Reference)it.next();
            boolean refValid=ref.validate(valContext);
          
            //System.out.println("Revisando elemento "+ref.getDigestValue());
            
            System.out.println(String.format("El estatus del elemento %s ref['%s'] de la firma es: %s", i, j, refValid));
            
            if(refValid==false)
            {
              InputStream is=null;
              byte[] datos=new byte[1024];
              int leidos=0;
              
              is=ref.getDigestInputStream();
              
              if(is!=null)
              {
                leidos=is.read(datos);
                is.close();
              }
              
              System.out.println(String.format("El elemento %s está alterado:", j));
              System.out.println(new String(datos, 0, leidos));
            }
                                                                                                                        
          } // Fin for j
        
        }
        else
        {
          System.out.println(String.format("La firma %s pasó la validación central.", i));
          System.out.println("La firma es fidedigna y el documento no está alterado.");
        }
        
      }  // Fin for i
      
      
      System.out.println("Terminó el proceso de validación.");
    }
    catch(ParserConfigurationException pcEx)
    {
      System.err.println("Excepción al tratar de configurar el parser: "+pcEx.getMessage());
    }
    catch(SAXException saxEx)
    {
      System.err.println("El parser no pudo leer el XML: "+saxEx.getMessage());
    }
    catch(InstantiationException insEx)
    {
      System.err.println("Excepción al tratar de obtener la instancia: "+insEx.getMessage());
    }
    catch(IllegalAccessException iaEx)
    {
      System.err.println("Excepción al tratar de obtener acceso a la clase: "+iaEx.getMessage());
    }
    catch(MarshalException mEx)
    {
      System.err.println("No se pudo obtener al elemento de la firma: "+mEx.getMessage());
    }
    catch(XMLSignatureException xsEx)
    {
      System.out.println("No se pudo verificar la firma XML: "+xsEx.getMessage());
    }
    catch(ClassNotFoundException cnfEx)
    {
      System.err.println("No se encontró la clase: "+cnfEx.getMessage());
    }
    catch(FileNotFoundException fnfEx)
    {
      System.err.println("No se pudo encontrar al archivo: "+fnfEx.getMessage());
    }
    catch(IOException iox)
    {
      System.err.println("Excepción al tratar de leer el archivo: "+iox.getMessage());
    }
    
    
  }  // Fin método main
  
  
  private static class KeyValueSelector extends KeySelector
  {
    
    public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException
    {
      if(keyInfo==null)
      {
        throw new KeySelectorException("KeyInfo es nulo.");
      }
      
      SignatureMethod sm=(SignatureMethod)method;
      List list=keyInfo.getContent();
      
      for(int i=0;i<list.size();i++)
      {
        XMLStructure xmlStructure=(XMLStructure)list.get(i);
        
        if(xmlStructure instanceof KeyValue)
        {
          PublicKey pk=null;
          
          try
          {
            pk=((KeyValue)xmlStructure).getPublicKey();
          }
          catch(KeyException ke)
          {
            throw new KeySelectorException(ke);
          }
          
          if(algEquals(sm.getAlgorithm(), pk.getAlgorithm()))
          {
            return new SimpleKeySelectorResult(pk);
          }
        }
      }
      
      throw new KeySelectorException("No se encontró ningún elemento KeyValue");
      
    }
    
    
    static boolean algEquals(String algURI, String algName)
    {
      if(algName.equalsIgnoreCase("DSA") && algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1))
      {
        return true;
      }
      else if (algName.equalsIgnoreCase("RSA") && algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1))
      {
        return true;
      }
      else
      {
        return false;
      }
                          
    }
    
  }  // Fin clase interna
  
  
  private static class SimpleKeySelectorResult implements KeySelectorResult
  {
    private PublicKey pk;
    
    SimpleKeySelectorResult(PublicKey pk)
    {
      this.pk=pk;
    }
    
    public Key getKey()
    {
      return pk;
    }
  } // Fin clase interna SimpleKeySelectorResult
  
}
