/*
 * Copyright
 *   2007 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 Lesser General Public License as published by
 * the Free Software Foundation; either version 3 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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;

/**
 *   classe pour encapsuler la verification d'une signature
 *
 */
import java.security.cert.X509Certificate;
import org.apache.log4j.Logger;
import java.util.Vector;

/**
 * class abstraite dont derivent les classes permettant de verifier les signatures de
 * fichiers XML
 * @author laurent.meckert
 */
public abstract class SignatureVerifier {

    static Logger logger = Logger.getLogger(SignatureVerifier.class);

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

    /**
     *  Appelle au besoin les initialisations necessaires
     */
    abstract void initialize() throws Exception;

    /**
     *  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()
     *  Si il y a un certificat X509 , ce certificat sera recuperable
     *  en appelant getCert()
     *@return  true si le digest est OK false sinon
     *@param s une chaine xml signee
     */
    abstract boolean verifyXml(String s) throws Exception;

    /**
     * Verifie la signature du fichier passee en parametre
     *@return  true si le digest est OK false sinon
     *@param s un fichier xml signe
     */
    abstract boolean verifyFile(String s) throws Exception;

    /**
     * Post traitement
     * pex signe ou recopie la signature dans des elements
     *@return  true si  OK false sinon
     *@param outfilename un fichier xml a creer
     */
    boolean postTraitement(String outfilename) throws Exception {
        return false;
    }

    /**
     * Procede au besoin aux nettoyages apres execution
     */
    abstract void terminate() throws Exception;

    /**
     *
     *@return true si il y a qq chose a faire apres la validation
     *
     */
    public boolean hasPostTrait() {
        return false;
    }

    /**
     *
     *@return true si il compte valider les certificats
     *
     */
    public boolean hasCertificatesValidation() {
        return false;
    }

    /**
     * Apres verification, on recupere ici la signature
     * proprement dite (en tant que chaine de caracteres)
     * @return une chaine xml
     */
    public String getSignatureAsXmlString() {
        if (signatureAsXmlString != null && !signatureAsXmlString.isEmpty()) {
            return signatureAsXmlString.get(0);
        }
        return null;
    }

    /**
     * Apres verification, on recupere ici la signature
     * proprement dite (en tant que chaine de caracteres)
     * @param i l'index dans la liste des signatures trouvees
     * @return une chaine xml
     */
    public String getSignatureAsXmlString(int i) {
        if (signatureAsXmlString != null && signatureAsXmlString.size() > i) {
            return signatureAsXmlString.get(i);
        }
        return null;
    }

    /**
     * ajoute signature a la collection de signature trouvees
     * @param signature
     */
    public void addSignatureAsXmlString(String signature) {
        if (signatureAsXmlString == null) {
            signatureAsXmlString = new Vector<String>();
        }
        signatureAsXmlString.add(signature);

    }

    /**
     * Ajoute le statut individuel de la signature au tableau
     * @param status
     */
    public void addindividualStatus(int status){
        if (individualStatus == null) {
            individualStatus = new Vector<Integer>();
        }
        individualStatus.add(new Integer(status));
    }
    
      /**
     *      * 
     * @param i indice
     * @return le certificat a l'indice i de la liste de certificats trouves
     */
    public Integer getIndividualStatus(int i) {
        if (individualStatus != null && individualStatus.size() > i) {
            return individualStatus.get(i);
        }
        return null;
    }

    
    
    /**
     * 
     * @return le premier certificat trouve
     */
    public X509Certificate getCert() {
        if (cert != null && !cert.isEmpty()) {
            return cert.get(0);
        }
        return null;
    }

    /**
     *      * 
     * @param i indice
     * @return le certificat a l'indice i de la liste de certificats trouves
     */
    public X509Certificate getCert(int i) {
        if (cert != null && cert.size() > i) {
            return cert.get(i);
        }
        return null;
    }

    /**
     * ajoute certif a la collection de certificats trouvees
     * @param certif
     */
    public void addCert(X509Certificate certif) {
        if (cert == null) {
            cert = new Vector<X509Certificate>();
        }
        cert.add(certif);

    }

    /**
     * @return le statut
     *
     */
    public int getStatus() {
        return status;
    }
    protected boolean doSchemaValidation = false;

    /**
     *
     * @return true si option validation de schema active
     */
    public boolean isDoSchemaValidation() {
        return doSchemaValidation;
    }

    /**
     *
     * @param doSchemaValidation
     */
    public void setDoSchemaValidation(boolean doSchemaValidation) {
        this.doSchemaValidation = doSchemaValidation;
    }
    /**
     * Peut s'averer necessaire pour libxml si on signe des IDREF
     */
    protected String externalDTD = null;

    /**
     * Donne le chemin du DTD externe
     * @param externalDTD un path valide
     */
    public void setExternalDTD(String externalDTD) {
        this.externalDTD = externalDTD;
        if (this.externalDTD == null) {
            this.externalDTD = "";
        }
    }

    /**
     *
     * @return le chemin du DTD externe
     */
    public String getExternalDTD() {
        return externalDTD;
    }
    /**
     * Nomnre de signatures trouvees
     */
    protected int foundSignatures;
    
    /**
     * XPath de recherche de signature;
     * 
     */
    protected String signatureXPath = "//ds:Signature";
    /**
     * Utile pour montrer les certificats en clair
     */
    protected Vector<X509Certificate> cert;
    /**
     * Le fragment XML de la signature, null si on ne l'a pas trouve
     */
    protected Vector<String> signatureAsXmlString;
    /**
     * statut/résultat globzl de la vérification
     */
    protected int status = 0;
    
    /**
     * statut/resultat individuel de la verification par signature
     */
    protected Vector<Integer> individualStatus;
    
    /**
     * Le code xml a ete correctement parse
     */
    public static final int PARSEOK = 1;
    /**
     * Le code xml a ete correctement valide sur le schema ou le DTD
     */
    public static final int SCHEMAOK = 2;
    /**
     * Il y  a bien une signature
     */
    public static final int SIGNATUREFOUND = 4;
    /**
     * Le digest calcule correspond bien a celui ecrit dans la signature
     */
    public static final int DIGESTVALID = 8;
    /**
     * la signature est contenue dans un certificat X509
     */
    public static final int ISX509CERT = 16;
    /**
     * le certificat X509 est valide (i.e. la chaine de validation remonte bien a un certificat de
     * confiance)
     */
    public static final int X509CERTVALID = 32;
    /**
     * le postrraitement s'est bien passe
     * 
     */
    public static final int POSTTRAITOK = 64;
    /**
     * Codes de gravite pour examen  rapide du resultat
     * tout va bien
     */
    public static final int VFY_GOOD = 0;
    /**
     * Codes de gravite pour examen  rapide du resultat
     * warning
     */
    public static final int VFY_BAD = 1;
    /**
     * Codes de gravite pour examen  rapide du resultat
     * erreur grave
     */
    public static final int VFY_VERYBAD = 2;

    // Methodes de confort pour detailler le statut
    /**
     *
     * @return true si le fichier XML a ete correctement parsé
     */
    public boolean parseOK() {
        return (status & PARSEOK) != 0 ? true : false;
    }

    /**
     *
     * @return true si la validation (schema ou DTD) a réussi
     */
    public boolean schemaOK() {
        return (status & SCHEMAOK) != 0 ? true : false;
    }

    /**
     *
     * @return true si on a trouvé une signature
     */
    public boolean signatureFound() {
        return (status & SIGNATUREFOUND) != 0 ? true : false;
    }

    /**
     *
     * @return true si la verification de la signature a reussi
     */
    public boolean digestValid() {
        return (status & DIGESTVALID) != 0 ? true : false;
    }

    /**
     *
     * @return true si la signature contient un certificat X509
     */
    public boolean isX509Cert() {
        return (status & ISX509CERT) != 0 ? true : false;
    }

    /**
     *
     * @return true si le certificat a pu etre valide en remontant jusqu'a un certificat de confiance
     */
    public boolean x509CertValid() {
        return (status & X509CERTVALID) != 0 ? true : false;
    }
    
    
    
    
      /**
     *
       * @param istatus 
       * @return true si la verification de la signature a reussi
     */
    public boolean digestValid(int istatus) {
        return (istatus & DIGESTVALID) != 0 ? true : false;
    }

    /**
     *
     * @param istatus 
     * @return true si la signature contient un certificat X509
     */
    public boolean isX509Cert( int istatus) {
        return (istatus & ISX509CERT) != 0 ? true : false;
    }

    /**
     *
     * @param istatus 
     * @return true si le certificat a pu etre valide en remontant jusqu'a un certificat de confiance
     */
    public boolean x509CertValid(int istatus) {
        return (istatus & X509CERTVALID) != 0 ? true : false;
    }

    /**
     *
     * @return true si le posttraitement s'est bien passe
     */
    public boolean posttraitOK() {
        return (status & POSTTRAITOK) != 0 ? true : false;
    }

    // Methodes de confort pour positionner le statut
    // accessibles que aux classes derivees
    protected void setParseOK(boolean b) {
        if (b) {
            status = status | PARSEOK;
        } else {
            status = status & ~PARSEOK;
        }
    }

    protected void setSchemaOK(boolean b) {
        if (b) {
            status = status | SCHEMAOK;
        } else {
            status = status & ~SCHEMAOK;


        }
    }

    protected void setSignatureFound(boolean b) {
        if (b) {
            status = status | SIGNATUREFOUND;
        } else {
            status = status & ~SIGNATUREFOUND;
        }
    }

    protected void setDigestValid(boolean b) {
        if (b) {
            status = status | DIGESTVALID;
        } else {
            status = status & ~DIGESTVALID;
        }
    }

    protected void setIsX509Cert(boolean b) {
        if (b) {
            status = status | ISX509CERT;
        } else {
            status = status & ~ISX509CERT;
        }
    }

    protected void setX509CertValid(boolean b) {
        if (b) {
            status = status | X509CERTVALID;
        } else {
            status = status & ~X509CERTVALID;
        }
    }
    
    
    protected int setDigestValid(boolean b, int istatus) {
        if (b) {
            istatus = istatus | DIGESTVALID;
        } else {
            istatus = istatus & ~DIGESTVALID;
        }
        return istatus;
    }

    protected int setIsX509Cert(boolean b, int istatus) {
        if (b) {
            istatus = istatus | ISX509CERT;
        } else {
            istatus = istatus & ~ISX509CERT;
        }
         return istatus;
    }

    protected int setX509CertValid(boolean b, int istatus) {
        if (b) {
            istatus = istatus | X509CERTVALID;
        } else {
            istatus = istatus & ~X509CERTVALID;
        } 
        return istatus;
    }
    
    

    protected void setPosttraitOK(boolean b) {
        if (b) {
            status = status | POSTTRAITOK;
        } else {
            status = status & ~POSTTRAITOK;
        }
    }

    /**
     * donne l'etat sous forme lisible (pour compte rendu)
     *@return une chaine de caracteres prete a l'affichage
     */
    public String getStatusAsString() {
        StringBuffer sb = new StringBuffer();
        if (!parseOK()) {
            sb.append("Le fichier n'est pas un fichier XML correct \n");
        }
        if (isDoSchemaValidation()) {
            if (schemaOK()) {
                sb.append("La validation d'apres le schema a réussi\n");
            } else {
                sb.append("La validation d'apres le schema a échoué\n");
            }
        }

        if (signatureFound()) {
            sb.append("" + getFoundSignatures() + " signature(s) trouvée(s)\n");
            sb.append("" + getValidatedSignatures() + " signature(s) valide(s)\n");
            if (digestValid()) {
                sb.append("La validation de la ou des signatures(s) a réussi!\n");
            } else {
                sb.append("La validation de la ou des signature(s) a ECHOUE\n ");
            }
        } else {
            sb.append("Aucune signature n'a été trouvée \n");
        }

        if (isX509Cert()) {

            if (hasCertificatesValidation()) { // tant qu'on n'a pas implemente la validation on ne mentionne pas les certificats
                // on ne mentionne pas les histoires de certificats ...
                sb.append("" + getFoundCertificates() + " certificat(s) X509 trouvé(s)\n");
                sb.append("" + getValidatedCertificates() + " certificat(s) X509 valide(s)\n");
                if (x509CertValid()) {
                    sb.append("La validation du ou des certificat(s) X509 a réussi\n");
                } else {
                    sb.append("La validation du ou des certificat(s) X509 a echoue\n");
                }
            }
        } else {
            sb.append("Aucun certificat X509 n'a été trouvé\n");
        }
        if (hasPostTrait()) {
            if (posttraitOK()) {
                sb.append("La post traitement du fichier a réussi\n");
            } else {
                sb.append("La post traitement du fichier a ECHOUE\n");
            }
        }
        return sb.toString();
    }

    /**
     * donne l'etat condense (pour compte rendu)
     *@return int une des 3 valeurs VFY_GOOD  VFY_BAD  VFY_VERYBAD
     */
    public int getStatusAsSeverity() {
        int res = VFY_BAD;
        if (!parseOK()) {
            return VFY_VERYBAD;
        } // meme pas du xml correct ..
        if (isDoSchemaValidation()) {
            if (!schemaOK()) {
                res = VFY_BAD; //on continue qd meme
            }
        }
        if (signatureFound()) {
            if (!digestValid()) {
                return VFY_VERYBAD;  // signature correspond pas
            }
        } else {
            return VFY_BAD;   // pas signé
        }
        // Si  ion passe ici c'est qu'il y a une signature et un digest valide
        if (isX509Cert() && hasCertificatesValidation()) {
            if (x509CertValid()) {
                return VFY_GOOD;
            } else {
                return VFY_BAD;
            }
        }

        if (hasPostTrait()) {
            if (posttraitOK()) {
                return res;
            } else {
                return VFY_VERYBAD;
            }
        } else {
            return res;
        }
    }

    /**
     *
     * @return le XPATH des nodes correspindant a une signature
     * par defaut = "//ds:Signature"
     */
    public String getSignatureXPath() {
        return signatureXPath;
    }

    /**
     * force un nouveau XPATH de recherche de signature
     * @param signatureXPath le nouveau XPATH handle with care ...
     */
    public void setSignatureXPath(String signatureXPath) {
        this.signatureXPath = signatureXPath;
    }

    /**
     * 
     * @return nombre de signatures trouvees
     */
    public int getFoundSignatures() {
        return foundSignatures;
    }

    /**
     * set nombre de signatures trouvees
     * @param foundSignatures
     */
    public void setFoundSignatures(int foundSignatures) {
        this.foundSignatures = foundSignatures;
    }

    /**
     * incremente nombre de signatures trouvees
     * 
     */
    public void incrFoundSignatures() {
        this.foundSignatures++;
    }
    protected int validatedSignatures = 0;

    /**
     * 
     * @return nombre de signatures valides
     */
    public int getValidatedSignatures() {
        return validatedSignatures;
    }

    /**
     * set nombre de signatures trouvees
     * @param validatedSignatures
     */
    public void setValidatedSignatures(int validatedSignatures) {
        this.validatedSignatures = validatedSignatures;
    }

    /**
     * incremente nombre de signatures trouvees
     * 
     */
    public void incrValidatedSignatures() {
        this.validatedSignatures++;
    }
    protected int foundCertificates;
    protected int validatedCertificates;

    /**
     * 
     * @return taille du tableau ou on stocke les certificats
     */
    public int getCertVectorSize() {
        if (cert == null) {
            return 0;
        }
        return cert.size();
    }

    /**
     * 
     * @return nombre de certificats trouves non nulls
     */
    public int getFoundCertificates() {
        int ret = 0;
        if (cert == null) {
            return 0;
        }
        for (int i = 0; i < cert.size(); ++i) {
            X509Certificate certif = getCert(i);
            if (certif != null) {
                ret++;
            }
        }
        return ret;
    }

    /**
     * 
     * @return nombre de certificats valides
     */
    public int getValidatedCertificates() {
        return validatedCertificates;
    }

    /**
     * set nombre de certificats valides
     * @param validatedCertificates
     */
    public void setValidatedCertificates(int validatedCertificates) {
        this.validatedCertificates = validatedCertificates;
    }

    /**
     * incremente le nombre de certificats valides
     * 
     */
    public void incrValidatedCertificates() {
        this.validatedCertificates++;
    }
}

   
    
    




