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

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import com.ibm.xml.dsig.*;
import com.ibm.xml.dsig.KeyInfo;
import com.ibm.xml.dsig.Reference;
import com.verisign.xmlsig.SigningKey;
import com.verisign.xmlsig.SigningKeyFactory;
import com.verisign.digsig.elements.*;
import com.verisign.messaging.WSSecurity;
import com.verisign.xpath.XPath;

import java.security.cert.X509Certificate;
import java.security.*;
import java.security.spec.InvalidKeySpecException;

import de.tu_dresden.diplom.richter_mirko_mat2628335.common.keystore.ApplicationSpecificKeystoreUtility;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.NS;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.handler.data.SignatureHandlingInfo;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.entity.IFEntity;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.entity.EntityPool;
import de.tu_dresden.diplom.richter_mirko_mat2628335.witness.entity.EntityManagerFactory;
import de.tu_dresden.diplom.richter_mirko_mat2628335.common.handler.data.SignatureHandlingInfo;

import javax.crypto.spec.DESedeKeySpec;
import javax.crypto.SecretKeyFactory;

/**
 * Utility-class that is being used when signing some XML-content
 */
public class SignUtil {

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

   /**
    * Sign using the enveloped paradigm and XSS4J-Iplementation.
    * @param doc The Document into which the result is being placed later.
    * @param target The element that shall be signed
    * @param signingEntity The entity with whose signature-key shall be signed
    * @throws SignatureStructureException
    * @throws XSignatureException
    */
   public static void signEnvelopedXSS4J(Document doc, Element target, IFEntity signingEntity) throws SignatureStructureException, XSignatureException {
      final String fn = "[signEnvelopedXSS4J]";
      if (logger.isDebugEnabled()) logger.debug(fn + " signing with entity '" + signingEntity + "'");
      TemplateGenerator siggen = new TemplateGenerator(doc, XSignature.SHA1, Canonicalizer.W3C2, SignatureMethod.DSA);
      // Default "true" leads to validity-failures on remote peer!
      siggen.setIndentation(false);
      //siggen.addReference(siggen.createReference("http://www.ibm.com/"));
      //siggen.addReference(siggen.createReference("http://www.alphaworks.ibm.com/"));

      Reference ref = siggen.createReference("");
      // ref.addXPathTransform("/Envelope/Header/CoordinationContext");
      ref.addTransform(Transform.ENVELOPED);
      //Reference ref = siggen.createReference((Element)target);
      siggen.addReference(ref);

      Element sigElement = siggen.getSignatureElement();
      target.appendChild(sigElement);

      //X509Certificate certificate = ApplicationSpecificKeystoreUtility.getX509Certificate4Signature(signingEntity);
      KeyInfo keyInfo = new KeyInfo();
      //keyInfo.setKeyValue(certificate.getPublicKey());
      keyInfo.setKeyNames(new String[]{ApplicationSpecificKeystoreUtility.SIG_KEYNAMES[signingEntity.getId()]});

      //KeyInfo.X509Data x5data = new KeyInfo.X509Data();
      //x5data.setCertificate(certificate);
      //x5data.setParameters(certificate, true, true, true);
      //keyInfo.setX509Data(new KeyInfo.X509Data[]{x5data});
      keyInfo.insertTo(sigElement);

      SignatureContext sigContext = new SignatureContext();
      //sigContext.setResourceShower(new EmbedderResourceShower());
      PrivateKey privateKey = ApplicationSpecificKeystoreUtility.getPrivateKey4Signature(signingEntity.getId());
      sigContext.sign(sigElement, privateKey);

      //target = (Element) Normalizer.normalize(target);
      if (logger.isDebugEnabled()) logger.debug(fn + " validating signed Element ...");
      VerifyUtil.validateEnvelopedXSS4J(target, new SignatureHandlingInfo());
      if (logger.isDebugEnabled()) logger.debug(fn + " ... done");
   }

   /**
    * Create the Signature using Verisigns TSIK-Dramework
    * @param doc The document into which the result shall be placed lateron
    * @param target The element that shall be signed
    * @param signingEntity The entity whose signature-key shall be used for signing
    */
   public static void signEnvelopedVerisign(Document doc, Element target, int signingEntity) {
      try {
         PrivateKey privateKey = ApplicationSpecificKeystoreUtility.getPrivateKey4Signature(signingEntity);
         SigningKey signingKey = SigningKeyFactory.makeSigningKey(privateKey);
         com.verisign.xmlsig.KeyInfo signingKeyInfo = new com.verisign.xmlsig.KeyInfo();
         signingKeyInfo.setCertificate(EntityPool.getEntity(signingEntity).getSignatureCertificate());
         //Key data = createTripleDESKey();
         WSSecurity wsse = new WSSecurity();
         //wsse.setSigningLocation(new XPath("//Envelope/Header/CoordinationContext"));
         wsse.sign(doc, signingKey, signingKeyInfo);

      } catch (GeneralSecurityException e) {
         e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      }
   }

   /**
    * Creates a triple-DES-key that is being used for hybrid encryption.
    * @return The new Triple-DES-key.
    * @throws NoSuchAlgorithmException
    * @throws InvalidKeyException
    * @throws InvalidKeySpecException
    */
   private static Key createTripleDESKey() throws NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException {
      byte[] rb = new byte[24];
      SecureRandom.getInstance("SHA1PRNG").nextBytes(rb);
      DESedeKeySpec keySpec = new DESedeKeySpec(rb);
      Key result = SecretKeyFactory.getInstance("DESede").generateSecret(keySpec);
      return result;
   }

}
