package kz.arta.nca_iiscon.util;

import kz.gov.pki.kalkan.asn1.knca.KNCAObjectIdentifiers;
import kz.gov.pki.kalkan.asn1.pkcs.PKCSObjectIdentifiers;
import kz.gov.pki.kalkan.jce.provider.KalkanProvider;
import kz.gov.pki.kalkan.xmldsig.KncaXS;
import org.apache.xml.security.encryption.XMLCipherParameters;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.signature.XMLSignatureException;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.soap.*;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

public class SignerUtil {


    private static Provider provider;

    public static Logger log = LoggerFactory.getLogger(SignerUtil.class);

    static {
        provider = new KalkanProvider();
        boolean exists = false;
        Provider[] providers = Security.getProviders();
        for (Provider p : providers) {
            if (p.getName().equals(provider.getName())) {
                exists = true;
            }
        }
        if (!exists) {
            Security.addProvider(provider);
            KncaXS.loadXMLSecurity();
        }

    }
    private synchronized static Certificate getCertificate(File keyPath, String keyPassword) throws Exception {
        try(InputStream inputStream = new FileInputStream(keyPath)) {
            KeyStore store = KeyStore.getInstance("PKCS12", provider.getName());
            store.load(inputStream, keyPassword.toCharArray());
            Enumeration<String> als = store.aliases();
            String alias = null;
            while (als.hasMoreElements()) {
                alias = als.nextElement();
            }

            final PrivateKey privateKey = (PrivateKey) store.getKey(alias, keyPassword.toCharArray());
            final X509Certificate x509Certificate = (X509Certificate) store.getCertificate(alias);
            SignerUtil signerUtil = new SignerUtil();
            Certificate certificate = signerUtil.new Certificate();
            certificate.setCert(x509Certificate);
            certificate.setPrivKey(privateKey);
            return certificate;
        } catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }


    public static Document getDocument(String xml) throws ParserConfigurationException, SAXException, IOException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        dbf.setNamespaceAware(true);
        DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
        return documentBuilder.parse(new ByteArrayInputStream(xml.getBytes()));
    }

    public static String signXML(String xml, File keyPath, String keyPassword) throws Exception {

        Certificate certificate = getCertificate(keyPath, keyPassword);
        return signXML(xml, certificate.getCert(), certificate.getPrivKey());

    }

    public static String signXML(String xml, X509Certificate cert, PrivateKey privKey) throws Exception {
        return createXmlSignature(privKey, cert, xml);
    }


    public static String createXmlSignature(PrivateKey key, X509Certificate cert, String xmlSource)
            throws ParserConfigurationException, SAXException, IOException, XMLSecurityException, TransformerException {

        Document document = getDocument(xmlSource);
        String signMethod;
        String digestMethod;

        String sigAlgOid = cert.getSigAlgOID();

        if (sigAlgOid.equals(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId())) {
            signMethod = Constants.MoreAlgorithmsSpecNS + "rsa-sha256";
            digestMethod = XMLCipherParameters.SHA256;
        } else if (sigAlgOid.equals(KNCAObjectIdentifiers.gost34311_95_with_gost34310_2004.getId())) {
            signMethod = Constants.MoreAlgorithmsSpecNS + "gost34310-gost34311";
            digestMethod = Constants.MoreAlgorithmsSpecNS + "gost34311";
        } else if (sigAlgOid.equals(KNCAObjectIdentifiers.gost3411_2015_with_gost3410_2015_512.getId())) {
            signMethod = "urn:ietf:params:xml:ns:pkigovkz:xmlsec:algorithms:gostr34102015-gostr34112015-512";
            digestMethod = "urn:ietf:params:xml:ns:pkigovkz:xmlsec:algorithms:gostr34112015-512";
        } else {
            throw new IllegalArgumentException("Incorrect algorithm: " + sigAlgOid);
        }

        Transforms transforms = new Transforms(document);
        transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
        transforms.addTransform(Transforms.TRANSFORM_C14N_WITH_COMMENTS);
        XMLSignature xmlSignature = new XMLSignature(document, "", signMethod);
        document.getFirstChild().appendChild(xmlSignature.getElement());
        xmlSignature.addDocument("", transforms, digestMethod);
        xmlSignature.addKeyInfo(cert);
        xmlSignature.sign(key);

        return getXmlString(document);
    }

    private static String getXmlString(Document document) throws TransformerException, IOException {
        try (StringWriter os = new StringWriter()) {
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer trans = tf.newTransformer();
            trans.transform(new DOMSource(document), new StreamResult(os));
            return os.toString();
        }
    }

    public boolean verifyXml(String xmlString) throws ParserConfigurationException, SAXException, IOException,
            XMLSignatureException, XMLSecurityException {

        Document doc = getDocument(xmlString);

        Element sigElement = null;
        Element rootEl = (Element) doc.getFirstChild();

        NodeList list = rootEl.getElementsByTagName("ds:Signature");
        if (list.getLength() == 0) {
            throw new IllegalStateException("ds:Signature not found");
        }

        Node sigNode = list.item(0);
        sigElement = (Element) sigNode;
        XMLSignature signature = new XMLSignature(sigElement, "");
        KeyInfo ki = signature.getKeyInfo();
        X509Certificate cert = ki.getX509Certificate();
        if (cert == null) {
            throw new IllegalStateException("Certificate not found");
        }
        return signature.checkSignatureValue(cert);
    }

    public class Certificate {
        private X509Certificate cert;
        private PrivateKey privKey;
        public X509Certificate getCert() {
            return cert;
        }
        public void setCert(X509Certificate cert) {
            this.cert = cert;
        }
        public PrivateKey getPrivKey() {
            return privKey;
        }
        public void setPrivKey(PrivateKey privKey) {
            this.privKey = privKey;
        }


    }


}


