package cz.feec.vutbr.WebApp;

import ch.qos.logback.core.net.SyslogOutputStream;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManagerFactory;


/**
 *
 * @author dzurenda
 */
public class TACRacsTLSserver {

    // TLS server port for listening
    // private final int PORT = 4433;  // CKP40
    private static int PORT = 4433;  // TESTING

    // big endian format

    private byte[] cardID      = new byte[]{(byte) 0x00,(byte) 0x00,(byte) 0x08,
            (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08};

    private byte[] terminalID  = new byte[]{(byte) 0x00,(byte) 0x00,(byte) 0x08,
            (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};

    private byte[] TLV_OK      = new byte[]{(byte) 0x00,(byte) 0x00,(byte) 0x01, (byte) 0x06};
    private byte[] TLV_ERR      = new byte[]{(byte) 0x00,(byte) 0x00,(byte) 0x01, (byte) 0x15};

    private byte[] TLV2_OK      = new byte[]{(byte) 0x01,(byte) 0x00,(byte) 0x01, (byte) 0x06};
    private byte[] TLV2_ERR      = new byte[]{(byte) 0x01,(byte) 0x00,(byte) 0x01, (byte) 0x15};

    // little endian format
    /*
    private byte[] cardID      = new byte[]{(byte) 0x00,(byte) 0x08, (byte) 0x00,
                                     (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08};

    private byte[] terminalID  = new byte[]{(byte) 0x00,(byte) 0x08, (byte) 0x00,
                                     (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};

    private byte[] TLV_OK      = new byte[]{(byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x06};
    private byte[] TLV_ERR     = new byte[]{(byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x15};
    */
    // select attributes which must be disclosed, i.e. in format 1,2.5 (discloses attributes 1, 2 and 5, the rest of attributes remains hidden)
    private String attDisclosed = "4";  // 4 - issued, 1,2,3 - not issued

    // select reader (0 - contactless smart card reader interface, 1 - contact smart card reader interface)
    private String terminal = "1";

    // set terminal in mode never ending authentication (true), only one authentication (false)
    private boolean inCycles = false;



    // main method of the application
    public void run(){

        try {

            // Create TLS server instance, TLS 1.2
            SSLContext context = SSLContext.getInstance("TLSv1.2");
            System.out.println("Loading terminal certificates and secret keys from keystore tacrEta.jks");

            // connect java keystore with terminal certificate and secret key
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
            KeyStore ks = KeyStore.getInstance("JKS");
            ks.load(new FileInputStream("tacrEta.jks"), "butattributte2020".toCharArray());
            // FileInputStream inputStream = (FileInputStream) ClassLoader.getSystemClassLoader().getResourceAsStream("tacrEta.jks");
            // ks.load(inputStream, "butattributte2020".toCharArray());

            kmf.init(ks, "1234".toCharArray());

            KeyStore trustKeyStore = KeyStore.getInstance("JKS");
            trustKeyStore.load(new FileInputStream("tacrEta.jks"), "butattributte2020".toCharArray());
            TrustManagerFactory trustMgrFactory = TrustManagerFactory.getInstance("SUNX509");
            trustMgrFactory.init(trustKeyStore);

            context.init(kmf.getKeyManagers(), trustMgrFactory.getTrustManagers(), null);

            // run TLS server
            SSLServerSocketFactory serverSocketFactory = context.getServerSocketFactory();
            SSLServerSocket server = (SSLServerSocket)serverSocketFactory.createServerSocket(PORT);
            System.out.println("TLS 1.2 server started, listening on "+server.getLocalSocketAddress());


            // waiting for client connection
            SSLSocket socket = (SSLSocket)server.accept();
            System.out.println("CKP.40 modul connected, connection from:"+socket.getInetAddress()+":"+socket.getPort());


            System.out.println("Establishing TLS connection with CKP.40 ...");
            DataOutputStream dOut = new DataOutputStream(socket.getOutputStream());

            // write data TLV, see https://www.baeldung.com/java-inputstream-server-socket

            // SEND TERMINAL ID (ACCESS DENY)
            System.out.println("TLS server -> CKP.40 "+bytesToHex(terminalID));
            dOut.write(terminalID);


            // read data TLV, see https://www.baeldung.com/java-inputstream-server-socket
            DataInputStream in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));

            // GET CKP40 ACK
            byte[] messageByte = readData(in, 4);
            System.out.println("TLS server <- CKP.40 "+bytesToHex(messageByte));

            // GET CKP40 RESULT
            byte[] messageByte2 = readData(in, 4);
            System.out.println("TLS server <- CKP.40 "+bytesToHex(messageByte2));

            // SEND ACK
            System.out.println("TLS server -> CKP.40 "+bytesToHex(TLV2_OK));
            dOut.write(TLV2_OK);

            System.out.println("TLS connection with CKP.40 established!");



        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(TACRacsTLSserver.class.getName()).log(Level.SEVERE, null, ex);
        } catch (KeyManagementException ex) {
            Logger.getLogger(TACRacsTLSserver.class.getName()).log(Level.SEVERE, null, ex);
        } catch (CertificateException ex) {
            Logger.getLogger(TACRacsTLSserver.class.getName()).log(Level.SEVERE, null, ex);
        } catch (KeyStoreException ex) {
            Logger.getLogger(TACRacsTLSserver.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnrecoverableKeyException ex) {
            Logger.getLogger(TACRacsTLSserver.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            System.out.println("Soubor tacrEta.jks nenalezen.");
            Logger.getLogger(TACRacsTLSserver.class.getName()).log(Level.SEVERE, null, ex);
        }
    }


    public static String bytesToHex(byte[] bytes) {

        char[] hexArray = "0123456789ABCDEF".toCharArray();
        char[] hexChars = new char[bytes.length * 2];
        for ( int j = 0; j < bytes.length; j++ ) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars);
    }

    byte[] readData(DataInputStream in, int size) throws IOException{

        byte[] messageByte = new byte[4];
        boolean end = false;

        int totalBytesRead = 0;
        while(!end) {
            int currentBytesRead = in.read(messageByte);
            totalBytesRead = currentBytesRead + totalBytesRead;

            if(totalBytesRead>=4) {
                end = true;
            }
        }

        return messageByte;
    }


}
