/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package de.christofreichardt.jaxws.securitypriceservice;

import de.christofreichardt.diagnosis.AbstractTracer;
import de.christofreichardt.diagnosis.LogLevel;
import de.christofreichardt.diagnosis.Traceable;
import de.christofreichardt.diagnosis.TracerFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.xml.crypto.MarshalException;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureException;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.ProtocolException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;

/**
 *
 * @author student
 */
public class VerificationHandler implements SOAPHandler<SOAPMessageContext>, Traceable {
  static public final String STORE_PASS = "changeit";
  
  @Override
  public boolean handleMessage(SOAPMessageContext soapMessageContext) {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("boolean", this, "handleMessage(SOAPMessageContext soapMessageContext)");
    
    try {
      boolean outbound = (boolean) soapMessageContext.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY);
      tracer.out().printfIndentln("outbound = %b", outbound);
      
      SOAPMessage soapMessage = soapMessageContext.getMessage();
      if (!outbound) {
        KeyStore keyStore = retrieveKeystore(soapMessageContext);
        
        try {
          SOAPHeader soapHeader = soapMessage.getSOAPHeader();
          Node signature = soapHeader.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature").item(0);
          if (signature == null)
            throw new ProtocolException("No Signature element found.");
          
          PublicKey publicKey = fetchPublicKey(soapMessage, keyStore);
          XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
          DOMValidateContext domValidateContext = new DOMValidateContext(publicKey, signature);
          domValidateContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
          domValidateContext.setURIDereferencer(new ServerSoapBodyDereferencer());
          XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
          boolean validated = xmlSignature.validate(domValidateContext);

          tracer.out().printfIndentln("validated = %b", validated);
          traceReferences(xmlSignature.getSignedInfo().getReferences(), domValidateContext);
          
          if (!validated) {
            tracer.logMessage(LogLevel.ERROR, "Core validation failed.", getClass(), "verifyPayloadSignature()");
            
            boolean status = xmlSignature.getSignatureValue().validate(domValidateContext);
            tracer.out().printfIndentln("status = %b", status);
            
            throw new ProtocolException("Invalid Signature.");
          }
        }
        catch (SOAPException | DOMException | GeneralSecurityException | MarshalException | XMLSignatureException ex) {
          tracer.logException(LogLevel.ERROR, ex, getClass(), "handleMessage(SOAPMessageContext soapMessageContext)");
        }
      }
      
      return true;
    }
    finally {
      tracer.wayout();
    }
  }
  
  private KeyStore retrieveKeystore(SOAPMessageContext soapMessageContext) {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("KeyStore", this, "retrieveKeystore(SOAPMessageContext soapMessageContext)");

    try {
      if (!soapMessageContext.containsKey("de.christofreichardt.jaxws.securitypriceservice.certificates")) {
        File certificatesFile = null;
        if (System.getProperties().containsKey("jboss.server.config.dir"))
          certificatesFile = new File(System.getProperty("jboss.server.config.dir") + File.separator + "certificates.jks");
        else if (System.getProperties().containsKey("com.sun.aas.instanceRoot"))
          certificatesFile = new File(System.getProperty("com.sun.aas.instanceRoot") + File.separator + "config" + File.separator + "certificates.jks");
        try {
          KeyStore keyStore = KeyStore.getInstance("JKS");
          if (certificatesFile != null) {
            try (FileInputStream inputStream = new FileInputStream(certificatesFile)) {
              keyStore.load(inputStream, STORE_PASS.toCharArray());
            }
          }
          else
            tracer.logMessage(LogLevel.WARNING, "No certificates found.", getClass(), "retrieveKeystore(SOAPMessageContext soapMessageContext)");
          soapMessageContext.put("de.christofreichardt.jaxws.securitypriceservice.certificates", keyStore);
        }
        catch (GeneralSecurityException | IOException ex) {
          tracer.logException(LogLevel.ERROR, ex, getClass(), "handleMessage(SOAPMessageContext soapMessageContext)");
        }
      }
      KeyStore keyStore = (KeyStore) soapMessageContext.get("de.christofreichardt.jaxws.securitypriceservice.certificates");
      
      return keyStore;
    }
    finally {
      tracer.wayout();
    }
  }
  
  private PublicKey fetchPublicKey(SOAPMessage soapMessage, KeyStore keyStore) throws SOAPException, KeyStoreException {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("PublicKey", this, "fetchPublicKey(SOAPMessage soapMessage, KeyStore keyStore)");

    try {
      SOAPBody soapBody = soapMessage.getSOAPBody();
      String userId = soapBody.getElementsByTagNameNS("http://de.christofreichardt/SecurityPriceService", "UserId").item(0).getTextContent();

      tracer.out().printfIndentln("userId = %s", userId);
      
      Certificate certificate = keyStore.getCertificate(userId);
      if (certificate == null)
        throw new ProtocolException("Unknown user: '" + userId + "'.");
      
      return certificate.getPublicKey();
    }
    finally {
      tracer.wayout();
    }
  }
  
  private void traceReferences(List<?> references, DOMValidateContext domValidateContext) throws XMLSignatureException {
    AbstractTracer tracer = getCurrentTracer();
    tracer.entry("void", this, "traceReferences(List<?> references)");
    
    try {
      try {
        Iterator iter = references.iterator();
        while (iter.hasNext()) {
          Reference reference = (Reference) iter.next();
          if (reference.getDigestInputStream() != null) {
            boolean refValidation = reference.validate(domValidateContext);
            
            tracer.out().printfIndentln("refValidation = %b", refValidation);
            
            try (InputStream inputStream = reference.getDigestInputStream();
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
              do {
                String line = bufferedReader.readLine();
                if (line == null) {
                  break;
                }
                tracer.out().printfIndentln("%s", line);
              } while (true);
            }
          }
          else
            tracer.out().printfIndentln("reference.getDigestInputStream() = %s", reference.getDigestInputStream());
        }
      }
      catch (IOException ex) {
        tracer.logException(LogLevel.ERROR, ex, getClass(), "traceReference(List<?> references)");
      }
    }
    finally {
      tracer.wayout();
    }
  }
  
//  private void traceSignatureMethod(SignatureMethod signatureMethod) {
//    AbstractTracer tracer = getCurrentTracer();
//    tracer.entry("void", this, "traceSignatureMethod(SignatureMethod signatureMethod)");
//
//    try {
//      tracer.out().printfIndentln("signatureMethod.getClass().getName() = %s", signatureMethod.getClass().getName());
//    }
//    finally {
//      tracer.wayout();
//    }
//  }
  
  @Override
  public Set<QName> getHeaders() {
    return Collections.emptySet();
  }
  
  @Override
  public boolean handleFault(SOAPMessageContext messageContext) {
    return true;
  }
  
  @Override
  public void close(MessageContext context) {
  }

  @Override
  public AbstractTracer getCurrentTracer() {
    return TracerFactory.getInstance().getCurrentQueueTracer();
  }
  
//  public boolean handleMessagex(SOAPMessageContext soapMessageContext) {
//    boolean outbound = (boolean) soapMessageContext.get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY);
//    SOAPMessage soapMessage = soapMessageContext.getMessage();
//    if (!outbound) {
//      KeyStore keyStore = retrieveKeystore(soapMessageContext);
//      try {
//        SOAPHeader soapHeader = soapMessage.getSOAPHeader();
//        Node signature = soapHeader.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature").item(0);
//        if (signature == null) {
//          throw new ProtocolException("No Signature element found.");
//        }
//        PublicKey publicKey = fetchPublicKey(soapMessage, keyStore);
//        XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance();
//        DOMValidateContext domValidateContext = new DOMValidateContext(publicKey, signature);
//        domValidateContext.setProperty("javax.xml.crypto.dsig.cacheReference", Boolean.TRUE);
//        domValidateContext.setURIDereferencer(new ServerSoapBodyDereferencer());
//        XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
//        boolean validated = xmlSignature.validate(domValidateContext);
//        traceReferences(xmlSignature.getSignedInfo().getReferences(), domValidateContext);
//        if (!validated) {
//          throw new ProtocolException("Invalid Signature.");
//        }
//      }
//      catch (SOAPException | DOMException | GeneralSecurityException | MarshalException | XMLSignatureException ex) {
//      }
//    }
//    return true;
//  }
  }
