/*
 * 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 br.com.ctecinf.nfe;

import br.com.ctecinf.Utils;
import br.com.ctecinf.Empresa;
import br.inf.portalfiscal.nfe.v100.evento.TEnvEvento;
import br.inf.portalfiscal.nfe.v400.autorizacao.TEnviNFe;
import br.inf.portalfiscal.nfe.v400.inutilizacao.TInutNFe;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 *
 * @author Cássio Conceição
 * @since 06/11/2018
 * @version 201811
 * @see http://ctecinf.com.br/
 */
public class Signature {

    private static void sign(Document document, String tag, String tagInf) throws Exception {

        KeyStore keyStore = KeyStore.getInstance("PKCS11");
        keyStore.load(null, Empresa.getSenhaCertificado().toCharArray());
        KeyStore.PrivateKeyEntry pkEntry = null;

        Enumeration<String> aliasesEnum = keyStore.aliases();
        while (aliasesEnum.hasMoreElements()) {
            String alias = (String) aliasesEnum.nextElement();
            if (keyStore.isKeyEntry(alias)) {
                pkEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, new KeyStore.PasswordProtection(Empresa.getSenhaCertificado().toCharArray()));
                break;
            }
        }

        if (pkEntry == null) {
            throw new Exception("Leitor não reconheceu certificado digital.");
        }

        XMLSignatureFactory sigFactory = XMLSignatureFactory.getInstance("DOM");
        KeyInfoFactory keyInfoFactory = sigFactory.getKeyInfoFactory();
        X509Data x509Data = keyInfoFactory.newX509Data(Arrays.asList((X509Certificate) pkEntry.getCertificate()));

        NodeList elements = document.getElementsByTagName(tagInf);
        Element el = (Element) elements.item(0);
        el.setIdAttribute("Id", true);
        String id = el.getAttribute("Id");

        Reference ref = sigFactory.newReference("#" + id, sigFactory.newDigestMethod(DigestMethod.SHA1, null), Arrays.asList(sigFactory.newTransform(Transform.ENVELOPED, (TransformParameterSpec) null), sigFactory.newTransform("http://www.w3.org/TR/2001/REC-xml-c14n-20010315", (TransformParameterSpec) null)), null, null);
        SignedInfo si = sigFactory.newSignedInfo(sigFactory.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec) null), sigFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null), Collections.singletonList(ref));
        XMLSignature signature = sigFactory.newXMLSignature(si, keyInfoFactory.newKeyInfo(Collections.singletonList(x509Data)));
        DOMSignContext dsc = new DOMSignContext(pkEntry.getPrivateKey(), document.getElementsByTagName(tag).item(0));

        signature.sign(dsc);
    }

    /**
     * Assina envio de NFC-e
     *
     * @param enviNFe
     * @return
     * @throws Exception
     */
    public static Document nfe(TEnviNFe enviNFe) throws Exception {

        Document document = Utils.str2Doc(Utils.marshaller(enviNFe));

        sign(document, "NFe", "infNFe");

        return document;
    }

    /**
     * Assina inutilização de numeração
     *
     * @param inutNFe
     * @return
     * @throws Exception
     */
    public static Document inutNFe(TInutNFe inutNFe) throws Exception {

        Document document = Utils.str2Doc(Utils.marshaller(inutNFe));

        sign(document, "inutNFe", "infInut");

        return document;
    }

    /**
     * Assina evento
     *
     * @param envEvento
     * @return
     * @throws Exception
     */
    public static Document evento(TEnvEvento envEvento) throws Exception {

        Document document = Utils.str2Doc(Utils.marshaller(envEvento));

        sign(document, "evento", "infEvento");

        return document;
    }
}
