/*
    Websocket Smartcard Signer
    Copyright (C) 2017  Damiano Falcioni ([email protected])
    
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.
    
    This program 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 Affero General Public License for more details.
    
    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>. 
 */
package df.sign;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.security.MessageDigest;
import java.security.Security;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.smartcardio.CardTerminal;
import javax.smartcardio.TerminalFactory;

import org.apache.commons.net.ntp.NTPUDPClient;
import org.apache.commons.net.ntp.TimeInfo;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.TeeOutputStream;

import df.sign.pkcs11.CertificateData;
import df.sign.utils.StringUtils;
import df.sign.utils.X509Utils;

@SuppressWarnings("restriction")
public class SignUtils {
    
    public static final String[] standardDllList = new String[]{"incryptoki2.dll", "bit4ipki.dll", "bit4opki.dll", "bit4xpki.dll", "OCSCryptoki.dll", "asepkcs.dll", "SI_PKCS11.dll", "cmP11.dll", "cmP11_M4.dll", "IpmPki32.dll", "IPMpkiLC.dll", "IpmPkiLU.dll", "bit4cpki.dll", "bit4p11.dll", "asepkcs.dll", "PKCS11.dll", "eTPKCS11.dll", "SSC_PKCS11.dll", "inp11lib.dll", "opensc-pkcs11.dll", "libbit4opki.so", "libbit4spki.so", "libbit4p11.so", "libbit4ipki.so", "opensc-pkcs11.so", "libeTPkcs11.so", "libopensc.dylib", "libbit4xpki.dylib", "libbit4ipki.dylib", "libbit4opki.dylib", "libASEP11.dylib", "libeTPkcs11.dylib"};    
    private static ArrayList<String[]> mapCardInfoList = new ArrayList<String[]>();
    public static final String logFilePath = System.getProperty("java.io.tmpdir")+"websocket_smartcard_signer.log";
    
    static {
        mapCardInfoList.add(new String[]{"Carta Raffaello 111", "bit4ipki.dll%incryptoki2.dll%libbit4ipki.so%libbit4ipki.dylib", "3BFF1800FF8131FE55006B02090200011101434E531131808E", "http://www.cartaraffaello.it/AreaDownload/tabid/80/language/it-IT/Default.aspx"});
        mapCardInfoList.add(new String[]{"Carta Raffaello 611", "bit4opki.dll%libbit4opki.so%libbit4opki.dylib", "3BFF1800008131FE45006B04050100012101434E5310318059", "http://www.cartaraffaello.it/AreaDownload/tabid/80/language/it-IT/Default.aspx"});
    }
    
    public static void initLog() throws Exception {
        File logFile = new File(logFilePath);
        logFile.delete();
        logFile.createNewFile();
        
        System.setOut(new PrintStream(new TeeOutputStream(System.out, new FileOutputStream(logFile)), true));
        System.setErr(new PrintStream(new TeeOutputStream(System.err, new FileOutputStream(logFile)), true));
    }
    
    public static ArrayList<CertificateData> processCertificateList(ArrayList<CertificateData> certificateDataList){
        ArrayList<CertificateData> nonRepudList = new ArrayList<CertificateData>();
        ArrayList<CertificateData> signList = new ArrayList<CertificateData>();
        
        for(CertificateData certificateData:certificateDataList){
            if(X509Utils.checkIsNonRepudiation(certificateData.cert))
                nonRepudList.add(certificateData);
            if(X509Utils.checkIsForSigning(certificateData.cert))
                signList.add(certificateData);
        }
        if(nonRepudList.size()!=0)
            return nonRepudList;
        
        if(signList.size()!=0)
            return signList;
        
        return new ArrayList<CertificateData>();
    }
    
    public static byte[] calculateHASH(String digestOID, byte[] data) throws Exception{
        String digestName = "";
        
        try{
            if(Security.getProvider("BC") == null)
                Security.addProvider(new BouncyCastleProvider());
            
            if(digestOID.equals(CMSSignedDataGenerator.DIGEST_MD5))
                digestName = "MD5";
            if(digestOID.equals(CMSSignedDataGenerator.DIGEST_SHA1))
                digestName = "SHA-1";
            if(digestOID.equals(CMSSignedDataGenerator.DIGEST_SHA256))
                digestName = "SHA-256";
            if(digestOID.equals(CMSSignedDataGenerator.DIGEST_SHA384))
                digestName = "SHA-384";
            if(digestOID.equals(CMSSignedDataGenerator.DIGEST_SHA512))
                digestName = "SHA-512";
            
            if(digestName.equals(""))
                throw new Exception("Unsupported digestOID");
            
            MessageDigest md = MessageDigest.getInstance(digestName, "BC");
            md.update(data);
            
            byte[] hash = md.digest();

            return hash;
        }catch(Exception e){
            throw new Exception("Error on the generation for the Hash "+digestName+":\n"+e.getMessage());
        }
    }
    
    public static boolean isContainedIntoArray(long element, long[] elementList){
        for(Object el:elementList)
            if(el.equals(element))
                return true;
        return false;
    }
    
    public static String getLibraryFullPath(String pkcs11Library){
        if(new File(pkcs11Library).exists())
            return pkcs11Library;
        
        String OS = System.getProperty("os.name").toLowerCase();
        
        String[] pathList = new String[0];
        
        if(OS.contains("windows")){
            if(pkcs11Library.toLowerCase().endsWith("dll")){
                String systemRoot = System.getenv("SystemRoot");
                String programFiles = System.getenv("ProgramFiles");
                pathList = new String[]{
                    systemRoot + "\\pkcs11Libs\\" + pkcs11Library,
                    programFiles + "\\Oberthur Technologies\\AWP\\DLLs\\" + pkcs11Library,
                    systemRoot + "\\" + pkcs11Library,
                    systemRoot + "\\System32\\" + pkcs11Library
                };
            }
        } else {
            if(pkcs11Library.toLowerCase().endsWith("so") || pkcs11Library.toLowerCase().endsWith("dylib")){
                pathList = new String[]{
                    "/usr/lib/" + pkcs11Library,
                    "/usr/lib/pkcs11/" + pkcs11Library,
                    "/usr/lib/PKCS11/" + pkcs11Library,
                    "/usr/local/lib/" + pkcs11Library,
                    "/lib/" + pkcs11Library,
                    "/var/lib/" + pkcs11Library,
                    "/Library/" + pkcs11Library,
                    "/Library/OpenSC/lib/" + pkcs11Library,
                    "/Library/bit4id/pkcs11/" + pkcs11Library
                };
            }
        }
        
        for(String path:pathList)
            if(new File(path).exists())
                return path;
        
        return null;
    }

    public static String[] checkJarConflicts(){
        String ret = "";
        String[] dirs = System.getProperty("java.ext.dirs").split(";");
        for(String dir:dirs){
            File[] files = new File(dir).listFiles();
            if(files == null)
                continue;
            for(File file:files){
                if(file.isDirectory())
                    continue;
                String fileName = file.getName().toLowerCase();
                if(fileName.endsWith(".jar") && (fileName.contains("bcprov") || fileName.contains("bcpkix") || fileName.contains("itextpdf")  || fileName.contains("jna") || fileName.contains("iaik")))
                    ret += file.getAbsolutePath() + ";";
            }
        }
        if(ret == "")
            return new String[0];
        return ret.split(";");
    }
    
    public static ArrayList<String> getConnectedCardATR(){
        ArrayList<String> ret = new ArrayList<String>();
        try{
            List<CardTerminal> terminalList = TerminalFactory.getDefault().terminals().list();
            
            for(CardTerminal terminal:terminalList)
                if(terminal.isCardPresent()){
                    javax.smartcardio.Card card = terminal.connect("*");
                    ret.add(StringUtils.toHexString(card.getATR().getBytes()));
                    card.disconnect(false);
                }
        }catch(Exception ex){}
        return ret;
    }
    
    public static String[] getCardInfo(String atr){
        for(String[] mapCardInfo:mapCardInfoList)
            if(atr.equals(mapCardInfo[2]))
                return mapCardInfo;
        return null;
    }
    
    public static String getCardTypeFromDLL(String dll){
        for(String[] mapCardInfo:mapCardInfoList)
            if(mapCardInfo[1].contains(dll))
                return mapCardInfo[0];
        return "";
    }
    
    public static String getIDFromSubject(String certificateSubject){
        String ret = "";
        String CN = certificateSubject.substring(certificateSubject.indexOf("CN=")+3);
        int indexCN = CN.indexOf(',');
        if(indexCN == -1)
            indexCN = CN.length();
        CN = CN.substring(0, indexCN);
        if(CN.contains("/"))
            CN = CN.split("/")[0].substring(1);
        
        String O = "Not Defined";
        if(certificateSubject.contains("O=")){
            O = certificateSubject.substring(certificateSubject.indexOf("O=")+2);
            int indexO = O.indexOf(',');
            if(indexO == -1)
                indexO = O.length();
            O = O.substring(0, indexO);
            if(O.contains("/"))
                O = O.split("/")[0];
        }
        ret = CN + "    Org:" + O;
        return ret;
    }
    
    public static CertificateData getCertificateDataByID(String id, ArrayList<CertificateData> certList){
        for(CertificateData cert:certList)
            if(cert.id.equals(id))
                return cert;
        return null;
    }
    
    public static void playBeeps(int numBeeps){
        if(numBeeps<0)
            return;
        
        for(int i=0;i<numBeeps;i++){
            java.awt.Toolkit.getDefaultToolkit().beep();
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {} 
        }
    }
    
    public static byte[] base64Encode(byte[] data){
        return org.bouncycastle.util.encoders.Base64.encode(data);
    }
    
    public static byte[] base64Decode(byte[] data){
        return org.bouncycastle.util.encoders.Base64.decode(data);
    }
    
    public static Date getNTPDate() throws Exception{
        //FIXME: how to use system defined proxy here ?
        System.setProperty("java.net.useSystemProxies", "true");
        NTPUDPClient client = new NTPUDPClient();
        client.setDefaultTimeout(5000);
        TimeInfo response = client.getTime(InetAddress.getByName("pool.ntp.org"));
        return new Date(response.getReturnTime());
    }
}