package de.tu_dresden.diplom.richter_mirko_mat2628335.common.crypto;

import org.apache.log4j.Logger;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.*;
import com.ibm.xml.enc.*;
import com.ibm.xml.enc.util.KeyStoreKeyInfoResolver;
import com.ibm.xml.dsig.XSignatureException;
import com.ibm.xml.dsig.IDResolver;
import com.ibm.dom.util.DOMUtil;
import com.sun.org.apache.xerces.internal.parsers.DOMParser;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.keystore.ApplicationSpecificKeystoreUtility;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.entity.IFEntity;
import de.tu_dresden.diplom.richter_mirko_mat2628335.Configuration;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.xml.transform.TransformerException;
import java.security.*;
import java.security.cert.CertificateException;
import java.io.IOException;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;


/**
 * Untility class that supports encryption of XML-Elements.
 */
public class EncryptionUtil {

   private static final Logger logger = Logger.getLogger(EncryptionUtil.class);

   private static final Configuration conf = new Configuration(Configuration.APPLICATION_BASE);

   private static final String ENCRYPTION_TEMPLATE = conf.getModuleUsageCounterHome() + "/WEB-INF/templates/xss4j/encryption_template.xml";
   private static final String KEYINFO_TEMPLATE = conf.getModuleUsageCounterHome() + "/WEB-INF/templates/xss4j/keyinfo.xml";

   /**
    * Encrypt the given Element using XSS4J from IBM with the encryption key of the
    * given entity.
    * @param doc Docuemtn into which the result shall be placed
    * @param elementToEncrypt The XML-Element that shall be encrypted
    * @param encryptingEntity The entity with whose encryption-key the encryption shall be performed.
    * @throws NoSuchProviderException
    * @throws NoSuchAlgorithmException
    * @throws BadPaddingException
    * @throws IOException
    * @throws InvalidAlgorithmParameterException
    * @throws IllegalBlockSizeException
    * @throws StructureException
    * @throws InvalidKeyException
    * @throws KeyInfoResolvingException
    * @throws NoSuchPaddingException
    */
   public static void encryptXSS4J(Document doc, Element elementToEncrypt, IFEntity encryptingEntity) throws NoSuchProviderException, NoSuchAlgorithmException, BadPaddingException, IOException, InvalidAlgorithmParameterException, IllegalBlockSizeException, StructureException, InvalidKeyException, KeyInfoResolvingException, NoSuchPaddingException {
      final String fn = "[encryptXSS4J] ";
      try {
         if (logger.isDebugEnabled()) logger.debug(fn + "encrypting with entity '" + encryptingEntity + "'");
         AlgorithmFactoryExtn af = new AlgorithmFactoryExtn();
         EncryptionContext encryption = new EncryptionContext();
         Keyinfo ki = new Keyinfo(getKeyinfo());
         KeyInfoResolver kir = getKeyInfoResolver(ki, af, null, null);
         kir.setOperationMode(KeyInfoResolver.ENCRYPT_MODE);
         encryption.setAlgorithmFactory(af);
         encryption.setKeyInfoResolver(kir);
         //encryption.setKey(ApplicationSpecificKeystoreUtility.getCertificate4Encryption(encryptingEntity.getId()).getPublicKey());
         encryption.setData(elementToEncrypt);
         encryption.setEncryptedType((Element) getEncryptedType().cloneNode(true), null, null, null);
         encryption.setKey(null);
         encryption.encrypt();
         encryption.replace();
      } catch (SAXException e) {
         e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      } catch (CertificateException e) {
         e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      } catch (KeyStoreException e) {
         e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      }
   }

   /**
    * Returns the template as Element-representation that is used to be merged with the
    * encrypted data.
    * @return The template-element
    * @throws SAXException
    * @throws SAXNotRecognizedException
    * @throws IOException
    */
   private static Element getEncryptedType() throws SAXException, SAXNotRecognizedException, IOException {
      DOMParser parser = new DOMParser();
      parser.setErrorHandler(new ErrorHandler() {
         //
         // Methods of interface: ErrorHandler
         //

         public void warning(SAXParseException exception) throws SAXException {
            exception.printStackTrace();
         }

         public void error(SAXParseException exception) throws SAXException {
            exception.printStackTrace();
         }

         public void fatalError(SAXParseException exception) throws SAXException {
            throw exception;
         }
      });
      parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false);
      parser.parse(ENCRYPTION_TEMPLATE);
      return parser.getDocument().getDocumentElement();
   }

   /**
    * Get the keyInfo-Element containing information on how to obtain the
    * key to encrypt (default workflow form XSS4J).
    * @return
    * @throws SAXException
    * @throws SAXNotRecognizedException
    * @throws IOException
    */
   private static Element getKeyinfo() throws SAXException, SAXNotRecognizedException, IOException {
      DOMParser parser = new DOMParser();
      parser.setErrorHandler(new ErrorHandler() {
         //
         // Methods of interface: ErrorHandler
         //

         public void warning(SAXParseException exception) throws SAXException {
            exception.printStackTrace();
         }

         public void error(SAXParseException exception) throws SAXException {
            exception.printStackTrace();
         }

         public void fatalError(SAXParseException exception) throws SAXException {
            throw exception;
         }
      });
      parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false);
      parser.parse(KEYINFO_TEMPLATE);
      return parser.getDocument().getDocumentElement();
   }

   /**
    * Gets the keyinfo-Resolver (XSS4J)
    * @param keyinfo
    * @param factory
    * @param idRes
    * @param retriever
    * @return
    * @throws CertificateException
    * @throws IOException
    * @throws KeyStoreException
    * @throws NoSuchAlgorithmException
    * @throws NoSuchProviderException
    */
   static KeyInfoResolver getKeyInfoResolver(Keyinfo keyinfo, AlgorithmFactoryExtn factory,
                                             IDResolver idRes,
                                             EncryptedKeyRetriever retriever)
        throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException,
        NoSuchProviderException {
      return getKeyInfoResolver(keyinfo, factory, idRes, retriever, getKeyStore(keyinfo));
   }

   /**
    * gets the keyInfoResolver (XSS4J)
    * @param keyinfo
    * @param factory
    * @param idRes
    * @param retriever
    * @param store
    * @return
    */
   static KeyInfoResolver getKeyInfoResolver(Keyinfo keyinfo, AlgorithmFactoryExtn factory,
                                             IDResolver idRes,
                                             EncryptedKeyRetriever retriever,
                                             KeyStore store) {
      KeyStoreKeyInfoResolver kiRes = new KeyStoreKeyInfoResolver(store);
      kiRes.setAlgorithmFactory(factory);
      kiRes.setIdResolver(idRes);
      kiRes.setEncryptedKeyRetriever(retriever);
      for (Iterator i = keyinfo.fAlias2Keypass.keySet().iterator(); i.hasNext();) {
         String s = (String) i.next();
         kiRes.putAliasAndPassword(s, (char[]) keyinfo.fAlias2Keypass.get(s));
      }

      return kiRes;
   }

   /**
    * Load the keystore using the information within given keyInfo.
    * @param keyinfo The keyinfo with the information where to obtain the keystore from.
    * @return
    * @throws CertificateException
    * @throws IOException
    * @throws KeyStoreException
    * @throws NoSuchAlgorithmException
    * @throws NoSuchProviderException
    */
   static KeyStore getKeyStore(Keyinfo keyinfo)
        throws CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException,
        NoSuchProviderException {
      String s = keyinfo.fStoretype;
      if (s == null) {
         s = KeyStore.getDefaultType();
      }
      KeyStore store = null;
      if (keyinfo.fProvider != null) {
         store = KeyStore.getInstance(s, keyinfo.fProvider);

      } else {
         store = KeyStore.getInstance(s);
      }

      s = keyinfo.fStorename;
      if (s == null) {
         s = System.getProperty("user.home") + File.separator + ".keystore";
      }
      File f = new File(s);
      InputStream is = null;
      if (f.exists()) {
         is = new FileInputStream(f);
      }
      store.load(is, keyinfo.fStorepass);

      return store;
   }

//   /**
//    * Imported from DOMCipher
//    * @param name
//    * @return
//    * @throws IOException
//    * @throws SAXException
//    */
//   static Document getDocument(String name) throws IOException, SAXException {
//      DOMParser dp = new DOMParser();
//      dp.setErrorHandler(new ErrorHandler() {
//         //
//         // Methods of interface: ErrorHandler
//         //
//
//         public void warning(SAXParseException exception) throws SAXException {
//            printStackTrace(exception);
//         }
//
//         public void error(SAXParseException exception) throws SAXException {
//            printStackTrace(exception);
//         }
//
//         public void fatalError(SAXParseException exception) throws SAXException {
//            throw exception;
//         }
//      });
//      dp.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false);
//      dp.parse(name);
//
//      return dp.getDocument();
//   }
//
//   /**
//    * Imported from DOMCipher
//    * @param thr
//    */
//   static void printStackTrace(Throwable thr) {
//      if (thr instanceof SAXException) {
//         Throwable t = ((SAXException) thr).getException();
//         if (t != null) {
//            thr = t;
//         }
//      } else if (thr instanceof TransformerException) {
//         Throwable t = ((TransformerException) thr).getException();
//         if (t != null) {
//            thr = t;
//         }
//      } else if (thr instanceof XSignatureException) {
//         Throwable t = ((XSignatureException) thr).getException();
//         if (t != null) {
//            thr = t;
//         }
//      }
//      thr.printStackTrace();
//   }

   /**
    * Class that represents a keyInfo-Object (alias, password, keystore, keystore-password)
    */
   static class Keyinfo {
      //
      // Data
      //

      String fStorename;
      String fStoretype;
      char[] fStorepass;
      String fProvider;
      Map fAlias2Keypass;

      private String alias;
      private char[] pass;


      //
      // Constructors
      //

      Keyinfo(Element keyinfo) {
         if (!isOfType(keyinfo)) {
            throw new IllegalArgumentException("Not keyinfo element");
         }

         fAlias2Keypass = new HashMap();
         init(keyinfo);
      }


      //
      // Methods
      //

      static boolean isOfType(Element elem) {
         return (elem != null && "keyinfo".equals(elem.getTagName()));
      }

      private void init(Node node) {
         for (Node n = node.getFirstChild(); n != null; n = n.getNextSibling()) {
            init(n);
         }

         if (node.getNodeType() == Node.ELEMENT_NODE) {
            String s = node.getNodeName();
            if (s.equals("alias")) {
               alias = DOMUtil.getStringValue(node);

            } else if (s.equals("key")) {
               fAlias2Keypass.put(alias, pass);
               alias = null;
               pass = null;

            } else if (s.equals("keystore")) {
               fStorepass = pass;
               pass = null;

            } else if (s.equals("name")) {
               fStorename = DOMUtil.getStringValue(node);

            } else if (s.equals("password")) {
               pass = DOMUtil.getStringValue(node).toCharArray();

            } else if (s.equals("provider")) {
               fProvider = DOMUtil.getStringValue(node);

            } else if (s.equals("type")) {
               fStoretype = DOMUtil.getStringValue(node);
            }
         }
      }
   }

   public static void main(String[] args){
//      DOMCipher.main(new String[]{"-e",
//                                  "*",
//                                  "E:\\home\\mirko\\diplom\\ext_app\\ibm_xml_security_suite\\xss4j\\data\\enc\\keyinfo1.xml",
//                                  "E:\\home\\mirko\\diplom\\ext_app\\ibm_xml_security_suite\\xss4j\\data\\enc\\bookorder.xml",
//                                  "//*[name()='cardinfo']",
//                                  "E:\\home\\mirko\\diplom\\ext_app\\ibm_xml_security_suite\\xss4j\\data\\enc\\template1.xml"
//      });
   }

}
