/*
 * Copyright
 *   2008 axYus - www.axyus.com
 *   2008 L.Meckert - laurent.meckert@axyus.com
 *
 * This file is part of XEMELIOS.
 *
 * XEMELIOS 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.
 *
 * XEMELIOS 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with XEMELIOS; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package fr.gouv.finances.cp.xemelios.plugin.signature;

import java.io.File;
//import java.io.FileInputStream;
import java.io.StringWriter;
import javax.xml.transform.stream.StreamResult;

import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource;


import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;


import java.security.Provider;
import java.security.Security;

import java.security.PublicKey;
import java.security.cert.X509Certificate;

import javax.xml.namespace.NamespaceContext;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.XMLUtils;

//import org.apache.xpath.XPathAPI;

import org.w3c.dom.NodeList;
import org.w3c.dom.Element;

import org.apache.log4j.Logger;

/**
 *   classe pour encapsuler la verification d'une signature
 *  Implementation avec la librarie Apache Xml Security (d'ou le prefixe APXS)
 */
public class APXSSignatureVerifier extends SignatureVerifier {

   protected org.w3c.dom.Document doc;
   protected Element nscontext;
   protected Element sigElement;
   protected NamespaceContext nsCtx;
   static Logger logger = Logger.getLogger(APXSSignatureVerifier.class);

   /**
    *  Constructeur sans parametre
    */
   public APXSSignatureVerifier() {
   }

   public boolean hasCertificatesValidation() {
      return false;

   }

   public boolean hasPostTrait() {
      return false;
   }

   public boolean postTraitement(String outfilename) throws Exception {
      return false;
   }

   /**
    *  Appelle au besoin les initialisations necessaires
    * @throws java.lang.Exception
    */
   public void initialize() throws Exception {
   }

   public void setNsCtx(NamespaceContext nsCtx) {
      this.nsCtx = nsCtx;
   }

   /**
    *  Verifie la signature de la chaine Xml passee en parametre
    *  le statut complet ( de type bitmask)  sera accessible par getStatus()
    *  si succes = la signature proprement dite sera recuperable
    *  en appelant getSignatureAsXmlString()
    *@return  true si le digest est OK false sinon
    *@throws java.lang.Exception
    * @param s une chaine xml signee
    */
   public boolean verifyXml(String s) throws Exception {
      return false;

   }

   protected String nodeAsString(Element element) {
      try {
         DOMSource domSource = new DOMSource(element);
         StringWriter writer = new StringWriter();
         StreamResult result = new StreamResult(writer);
         TransformerFactory tf = TransformerFactory.newInstance();
         Transformer transformer = tf.newTransformer();
         transformer.transform(domSource, result);
         return writer.toString();
      } catch (Exception e) {
         e.printStackTrace();
         return null;
      }

   }

   /**
    * Verifie la signature du fichier passee en parametre
    *@return  true si le digest est OK false sinon
    *@throws java.lang.Exception
    *@param signatureFileName le fichier a verifier
    *
    */
   public boolean verifyFile(String signatureFileName) throws Exception {
      boolean isSignatureValid = false;

      javax.xml.parsers.DocumentBuilderFactory dbf =
         javax.xml.parsers.DocumentBuilderFactory.newInstance();

      if (doSchemaValidation) {
         dbf.setAttribute("http://apache.org/xml/features/validation/schema",
                          Boolean.TRUE);
         dbf.setAttribute(
                          "http://apache.org/xml/features/dom/defer-node-expansion",
                          Boolean.TRUE);
         dbf.setValidating(true);
         dbf.setAttribute("http://xml.org/sax/features/validation",
                          Boolean.TRUE);
      }

      dbf.setNamespaceAware(true);
      dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE);


      File f = new File(signatureFileName);

      System.out.println("Try to verify " + f.toURL().toString());

      javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();

      //Set the error handler
      Validator handler = new Validator();
      db.setErrorHandler(handler);



      doc = db.parse(new java.io.FileInputStream(f));


      if (doc != null) {
         setParseOK(true);
      } else {
         System.out.println("echec parse ");
      }

      if (doSchemaValidation) {
         setSchemaOK(!handler.validationError);
      }

      // nscontext = XMLUtils.createDSctx(doc, "ds",
      //                                 Constants.SignatureSpecNS);
      // LM ajout  d'un ns plus ou moins arbitraire pour le plus haut niveau PES_Aller
     //  nscontext.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:n",
//                                "http://www.minefi.gouv.fr/cp/helios/pes_v2/Rev0/aller");

      System.out.println("Recherche node signaturr " + getSignatureXPath());

      NodeList nl = null;
      if(nsCtx == null) {
         //  nl  = XPathAPI.selectNodeList(doc, getSignatureXPath(), nscontext);
      } else {
         XPathFactory xpf = XPathFactory.newInstance();
         XPath xp = xpf.newXPath();
         xp.setNamespaceContext(nsCtx);
       
         nl = (NodeList)xp.evaluate(getSignatureXPath(), doc, XPathConstants.NODESET);
      }

      if (nl != null && nl.getLength() > 0) {
         System.out.println("" + nl.getLength() + " signature(s) detectee(s))");
         logger.info("" + nl.getLength() + " signature(s) detectee(s))");
         setDigestValid(true);
         for (int i = 0; i < nl.getLength(); ++i) {
            int istatus = 0; // status individuel de la signature
            //   ancienne version sans boucle  sigElement = (Element) XPathAPI.selectSingleNode(doc,
            //    "//ds:Signature[1]", nscontext);

            sigElement = (Element) nl.item(i);

            if (sigElement != null) {
               setSignatureFound(true);
               incrFoundSignatures();
            }

            XMLSignature signature = new XMLSignature(sigElement,
                                                      f.toURL().toString());

            //  XMLUtils.outputDOMc14nWithComments(doc, System.out);


            addSignatureAsXmlString(nodeAsString(sigElement));

            //System.out.println(nodeAsString(doc.getDocumentElement()));

            // APXSGSVDebug.debugDoc(doc);
            // XMLUtils.outputDOMc14nWithComments(signature.getElement(), System.out);
            KeyInfo ki = signature.getKeyInfo();

            if (ki != null) {
               if (ki.containsX509Data()) {
                  System.out.println("Could find a X509Data element in the KeyInfo");
               }

               X509Certificate certif = signature.getKeyInfo().getX509Certificate();

               addCert(certif); // on les ajoute meme null pour que le tableau
               // des certificats soit homogene a celiui des signatures
               if (certif != null) {
                  setIsX509Cert(true);
                  istatus = setIsX509Cert(true, istatus);

                  //    System.out.println(
                  //    "I try to verify the signature using the X509 Certificate: "
                  //    + cert);
                  isSignatureValid = signature.checkSignatureValue(certif); //
                  //                        System.out.println("The XML signature in file " + f.toURL().toString() + " is " + (isSignatureValid
                  //                                ? "valid (good)"
                  //                                : "invalid !!!!! (bad)"));
                  if (isSignatureValid) {
                     incrValidatedSignatures();
                     istatus = setDigestValid(true, istatus);
                  } else {
                     setDigestValid(false);
                  }
               } else {
                  System.out.println("Did not find a Certificate");
                  addCert(null);
                  PublicKey pk = signature.getKeyInfo().getPublicKey();

                  if (pk != null) {

                     isSignatureValid = signature.checkSignatureValue(pk); //

                     if (isSignatureValid) {
                        incrValidatedSignatures();
                        istatus = setDigestValid(true, istatus);
                     } else {
                        setDigestValid(false);
                     }
                  } else {
                     System.out.println(
                                        "Did not find a public key, so I can't check the signature");
                     setDigestValid(false);
                  }
               }
            } else {
               System.out.println("Did not find a KeyInfo");
            }
            this.addindividualStatus(istatus);
         }

      }
      if (!signatureFound()) {
         setDigestValid(false);
      }

      return digestValid();
   }

   private class Validator extends DefaultHandler {

      public boolean validationError = false;
      public SAXParseException saxParseException = null;

      @Override
         public void error(SAXParseException exception)
         throws SAXException {
         System.err.println(exception);
         logger.error(exception);
         validationError = true;
         saxParseException = exception;
      }

      @Override
         public void fatalError(SAXParseException exception)
         throws SAXException {
         System.err.println(exception);
         logger.error(exception);
         //  exception.print();
         validationError = true;
         saxParseException = exception;
      }

      @Override
         public void warning(SAXParseException exception)
         throws SAXException {
      }
   }

   /**
    * Procede au besoin aux nettoyages apres execution
    * @throws java.lang.Exception
    */
   public void terminate() throws Exception {
   }
  

   static {
      org.apache.xml.security.Init.init();
      Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
  }

   /**
    *
    * @param args
    */
   public static void main(String args[]) {
      // check args
      String fileToVerify = null;
      String optionalDTD = null;
      if (args.length > 0) {
         fileToVerify = args[0];
      } else {
         fileToVerify = "C:\\tt1.xml";
         //            System.err.println("Usage java APXSGlobalSignatureVerifier <fichier_a_verifier>");
         //            System.exit(1);
      }

      APXSSignatureVerifier verifier = new APXSSignatureVerifier();

      try {
         verifier.initialize();
         verifier.verifyFile(fileToVerify);
         for (int i = 0; i < verifier.getCertVectorSize(); ++i) {
            X509Certificate cert = verifier.getCert(i);
            if (cert != null) {
               System.out.println("PRINTING CERT=");
               System.out.println(cert);
            }
         }
         System.out.println(verifier.getStatusAsString());
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}


