/*
 * Copyright
 *   2008 axYus - www.axyus.com
 *   2008 L.Meckert - laurent.meckert.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.signerfichier;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;


import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.PrivateKey;

import java.security.Key;


import java.util.Enumeration;

/**
 *
 * @author laurent.meckert
 */
public class KeystoreFiller implements IKeystoreValider {

   /**
    * Le fichier des cles a ete correctement charge
    */
   public static final int LOADKSOK = 2;
   /**
    * La cle privee est bien charge
    */
   public static final int LOADPKOK = 4;
   /**
    * L'algorithme de la cle privee est correct
    */
   public static final int ALGORITHMOK = 8;
   int status = 0;
   KeyStore targetKs = null;
   KeyStore inputKs = null;
   private String keystorePassword;
   private File keystoreFile;
   private String alias;
   private String aliasPassword;
   private DlgKeystore dlgKeystore;
   private IStatusReporter statusReporter;
   private String statusString = null;

   /**
    *
    * @return true si le keystore est bien charge
    */
   public boolean loadKsOK() {
      return (status & LOADKSOK) != 0 ? true : false;
   }

   /**
    *
    * @return true si la cle privee est bien charge
    */
   public boolean loadPkOK() {
      return (status & LOADPKOK) != 0 ? true : false;
   }

   /**
    *
    * @return true si la cle privee est bien charge
    */
   public boolean algoIdOK() {
      return (status & ALGORITHMOK) != 0 ? true : false;
   }

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

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

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

   protected static String kstypeFromSuffix(String suffix) {
      if (suffix.equalsIgnoreCase("jks") || suffix.equalsIgnoreCase("ks")) {
         return "JKS";
      }
      if (suffix.equalsIgnoreCase("jce")) {
         return "JCEKS";
      }
      if (suffix.equalsIgnoreCase("p12") || suffix.equalsIgnoreCase("pfx")) {
         return "PKCS12";
      }
      return null;
   }

   KeystoreFiller() throws Exception {
      targetKs = KeyStore.getInstance(kstypeFromSuffix("pfx"));
      targetKs.load(null, null);
   }

   KeystoreFiller(File keystoreFile, String keystorePassword) throws Exception {
      targetKs = KeyStore.getInstance(kstypeFromSuffix("pfx"));

      FileInputStream fis = new FileInputStream(keystoreFile);
      System.out.println("loading " + keystoreFile.getName());
      //load the keystore
      targetKs.load(fis, keystorePassword.toCharArray());
   }

   public void loadKeystore(File keystoreFile, String keystorePassword) throws Exception {
      int n = keystoreFile.getName().lastIndexOf('.');
      String extension = keystoreFile.getName().substring(n + 1);

      inputKs = KeyStore.getInstance(kstypeFromSuffix(extension));

      FileInputStream fis = new FileInputStream(keystoreFile);
      System.out.println("loading " + keystoreFile.getName());
      //load the keystore
      try {
         inputKs.load(fis, keystorePassword.toCharArray());
      } catch (Exception e) {
         setLoadKsOK(false);
         return;
      }
      setLoadKsOK(true);

   }

   public void importAliases(String newPassword) throws Exception {

      // parcourt les entrees et les recopie dans le  ks ppal que l'on
      // est en train de creer ou de completer
      for (Enumeration e = inputKs.aliases(); e.hasMoreElements();) {
         String curalias = (String) e.nextElement();
         //  System.out.println("testing " + curalias);
         if (inputKs.isCertificateEntry(curalias) || inputKs.isKeyEntry(curalias)) {
            System.out.println(curalias + "isCertificateEntry");

            setAlias(curalias);
            setAliasPassword(this.getKeystorePassword()); //test 1 avec mdp
            //Charge l'alias avec le password qu'on vient d'affecter
            // et le recopie avec le nouveau password
            loadAlias(newPassword);
            if (!loadPkOK()) {
               if (algoIdOK()) {//Vraisemblablement pb de mot de passe
                  // on redonne des chances avec dialogue de mot de passe
                  dlgKeystore.setTitle("Ouverture de la cle privée  " + curalias);
                  dlgKeystore.setMode(DlgKeystore.MODE_ALIAS);
                  dlgKeystore.setVisible(true);
                  // et on reteste
                  if (!loadPkOK()) {
                     if (algoIdOK()) {//Vraisemblablement pb de mot de passe
                       // et on  on a épuisé le quota d'essais
                        setStatusString("Mauvais mot de passe");                    
                     } // sinon statusString a ete MAJ avec le pb d'algorithm
                     statusReporter.error(getStatusString(), "Clé non chargée");
                  }
               } else {// Algo non reconnu, irrecuperable
                  statusReporter.error(getStatusString(), "Clé non chargée");
               }
            }
         }
      }

   }

   public void saveKeystore(File outFile, String keystorePassword) throws Exception {

      if (targetKs != null) {
         FileOutputStream fos = new FileOutputStream(outFile);
         targetKs.store(fos, keystorePassword.toCharArray());
      }
   }

   public static void main(String[] args) {
      try {
         String password = "jna";
         File outFile = new File(args[0]);
         KeystoreFiller fks = new KeystoreFiller(outFile, password);

         for (int i = 1; i < args.length; ++i) {
            File cur = new File(args[i]);
            fks.loadKeystore(cur, password);
         }
         // password = "newpass";
         fks.saveKeystore(outFile, password);
      } catch (Exception e) {
         e.printStackTrace();
      }

   }

   public void setKeystorePassword(String password) {
      keystorePassword = password;
   }

   public String getKeystorePassword() {
      return keystorePassword;
   }

   public void loadKeystore() throws Exception {
      if (getKeystoreFile() != null) {
         loadKeystore(keystoreFile, keystorePassword);
      }
   }

   public void setAlias(String alias) {
      this.alias = alias;
   }

   public void setAliasPassword(String password) {
      this.aliasPassword = password;
   }

   public void loadAlias() {
      loadAlias(null);
   }

   public void loadAlias(String newPassword) {
      try {
         if (newPassword == null) {
            newPassword = aliasPassword;
         }
         this.setLoadPkOK(false);
         this.setAlgorithmOK(true);
         Certificate[] chain = inputKs.getCertificateChain(alias);
         Key key = inputKs.getKey(alias, aliasPassword.toCharArray());

         PrivateKey privateKey = (PrivateKey) key;
         String pkAlgo = privateKey.getAlgorithm();
         if (pkAlgo.equals("RSA") || pkAlgo.equals("DSA")) {
            targetKs.setKeyEntry(alias, key, newPassword.toCharArray(), chain);
            this.setLoadPkOK(true);
            this.setAlgorithmOK(true);
         } else {
            System.err.println("Algorithme non valable " + pkAlgo);

            setStatusString("La clé " + alias + " utilise un algorithme inconnu ou non utilisable : " + pkAlgo);


            this.setLoadPkOK(false);
            this.setAlgorithmOK(false);
         }

      } catch (Exception e) {
      }

   }

   /**
    * @return the keystoreFile
    */
   public File getKeystoreFile() {
      return keystoreFile;
   }

   /**
    * @param keystoreFile the keystoreFile to set
    */
   public void setKeystoreFile(File keystoreFile) {
      this.keystoreFile = keystoreFile;
   }

   /**
    * @return the dlgKeystore
    */
   public DlgKeystore getDlgKeystore() {
      return dlgKeystore;
   }

   /**
    * @param dlgKeystore the dlgKeystore to set
    */
   public void setDlgKeystore(DlgKeystore dlgKeystore) {
      this.dlgKeystore = dlgKeystore;
   }

   /**
    * @return the statusReporter
    */
   public IStatusReporter getStatusReporter() {
      return statusReporter;
   }

   /**
    * @param statusReporter the statusReporter to set
    */
   public void setStatusReporter(IStatusReporter statusReporter) {
      this.statusReporter = statusReporter;
   }

   /**
    * @return the statusString
    */
   public String getStatusString() {
      return statusString;
   }

   /**
    * @param statusString the statusString to set
    */
   public void setStatusString(String statusString) {
      this.statusString = statusString;
   }
}
