Obtención del Token de Servicio de Impuestos Internos SII

En la facturación electrónica, el Token es un elemento clave para la autenticación de los emisores ante el Servicio de Impuestos Internos. Este artículo explica cómo generar un Token de autenticación, para después procesarlo en la Petición http de upload.

¿Qué es un Token y por qué es importante?

El Token es una cadena de texto encriptada que valida la identidad del emisor y garantiza la integridad de las transacciones electrónicas. En el caso de los Documentos Tributarios Electrónicos (DTE), es esencial para autorizar el envío de facturas, boletas y otros documentos tributarios.


Preparación del entorno

Antes de comenzar, es importante tener los siguientes elementos:


Certificado digital (archivo .p12): Necesario para firmar el Token. Incluye una clave privada y un certificado asociado.

Clave del certificado: Utilizada para desbloquear el archivo .p12.

Bibliotecas estándar de Java: El código utiliza bibliotecas incluidas en el JDK para XML y criptografía.

Preparación del entorno

Antes de comenzar, es importante tener los siguientes elementos:

  1. Certificado digital (archivo .p12): Necesario para firmar el Token. Incluye una clave privada y un certificado asociado.
  2. Clave del certificado: Utilizada para desbloquear el archivo .p12.
  3. Bibliotecas estándar de Java: El código utiliza bibliotecas incluidas en el JDK para XML y criptografía.
  4. Semilla obtenida del Servicio de Impuestos Internos. En caso de no haberla generado anteriormente te recomiendo leer este artículo: https://jettyandbeyond.blogspot.com/2024/12/como-obtener-la-semilla-del-servicio-de.html


Implementación en Java

El proceso para obtener un Token incluye dos pasos principales:

Crear el XML de solicitud del Token.

Firmar digitalmente la semilla obtenida.

Creación del Mensaje SOAP de solicitud

El primer paso es generar el XML que servirá como base para la solicitud del Token. Este XML contiene la información de la semilla y la correspondiente firma digital realizada por el usuario del cerificado. Aquí hay un ejemplo de cómo podrías construir este XML:


/*
 * 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 setsimulacion;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
 *
 * @author esteban
 */
public class Token {
     String urlenvironment;
    public Token(String environment){
        
        this.urlenvironment = environment;
    }
    
       public String getToken(String valorsemilla,String pathcertificado,String clave,String nombredte) throws IOException, FileNotFoundException, ParserConfigurationException, SAXException, TransformerConfigurationException, TransformerException, Exception{
      
           
        valorsemilla = valorsemilla.replaceFirst ("^0*", "");
        Long cadenaResultadoInt = Long.parseLong(valorsemilla);
        String valuesemilla = Long.toString(cadenaResultadoInt);
          
       DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
	 DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
	 Document doc = docBuilder.newDocument();
         
         Element gettoken = doc.createElement("getToken");
         Element item = doc.createElement("item");
         
        
         Node semilla = doc.createElement("Semilla");
         semilla.setTextContent(valuesemilla);
         item.appendChild(semilla);
         
         gettoken.appendChild(item);
         doc.appendChild(gettoken);
        
         TransformerFactory transformerFactory = TransformerFactory.newInstance();
         Transformer transformer = transformerFactory.newTransformer();
	 DOMSource source = new DOMSource(doc);
         
         StringWriter writer = new StringWriter();
         
	 StreamResult result = new StreamResult(writer);
	
         transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
          transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
          transformer.setOutputProperty(OutputKeys.INDENT, "yes");
          transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
          transformer.transform(source, result);
	  System.out.println("Done");
           
          
          
        SignToken objfirma = new SignToken();
        StringWriter writer2 =  objfirma.signToken(writer, pathcertificado,clave);
           
           
           
           
        String valortoken = requestToken(writer2, this.urlenvironment);
           
           
           
           
           
           return valortoken;
           
           
 
       } 
    
    
    public String requestToken(StringWriter writer, String urlauth) throws IOException, FileNotFoundException, ParserConfigurationException, SAXException{
        
         String contenidosemilla = "";
           
          
         
       contenidosemilla = contenidosemilla + writer.toString() + "\n" ;
         
       String original1 = "<?xml version="+"\""+"1.0"+"\"" +"?>";
       String reemplazo1 = "";
       
    contenidosemilla = contenidosemilla.replace(original1,reemplazo1); 
    System.out.print(contenidosemilla);
       
      
   String direccion="https://"+urlauth+"/DTEWS/GetTokenFromSeed.jws?WSDL";
   
    URL url = new URL (direccion);
    HttpURLConnection conexion = (HttpURLConnection) url.openConnection();
   
        

       /* comienzo a armar la peticion soap para procesarla con wget */
        String inputsoap =   

        "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\""+ "\n"+
        "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\""+"\n"+
        "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""+"\n"+
        "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""+"\n"+
        "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"+"\n"+
        "<SOAP-ENV:Body>"+"\n"+
        "<m:getToken xmlns:m=\"https://"+urlauth+"/DTEWS/GetTokenFromSeed.jws\">"+"\n"+
        "<pszXml xsi:type=\"xsd:string\">"+"\n"+
        "<![CDATA["+"\n"+contenidosemilla+"]]>"+
        "</pszXml>"+"\n"+
        "</m:getToken>"+"\n"+
        "</SOAP-ENV:Body>"+"\n"+
        "</SOAP-ENV:Envelope>";
                
                
                
 ByteArrayOutputStream bout = new ByteArrayOutputStream();
 byte[] buffer = new byte[inputsoap.length()];
 buffer = inputsoap.getBytes();
 bout.write(buffer);
 byte[] b = bout.toByteArray();
        
  conexion.setRequestProperty("Content-Length",String.valueOf(b.length));
  
conexion.setRequestProperty("Access-Control-Allow-Credentials" ,"true");
conexion.setRequestProperty("Content-Type", "text/xml; charset=utf-8");
conexion.setRequestProperty("SOAPAction", "getToken");

conexion.setDoOutput(true);
conexion.setDoInput(true);
        
//Write the content of the request to the outputstream of the HTTP Connection.
        try (OutputStream out = conexion.getOutputStream()) {
            //Write the content of the request to the outputstream of the HTTP Connection.
            out.write(b);
             }

 String inputLine = new String();
String salida = new String();
        try (BufferedReader in = new BufferedReader(new InputStreamReader(conexion.getInputStream()))) {
                
            while ((inputLine = in.readLine()) != null)
                salida = salida + inputLine;
        
        }
        
        System.out.print(salida);
        
        salida = salida.replace("&lt;", "<").replace("&quot;","\"").replace("&gt;",">");
        
       String original = "<?xml version="+"\""+"1.0"+"\"" + " encoding="+"\""+ "UTF-8" + "\""+"?>";
       String reemplazo = "";
       
    salida = salida.replace(original,reemplazo); 
 
       String  valortoken = readFileToken(salida);
       return valortoken;       
}
    
public String readFileToken(String cadena) throws FileNotFoundException, IOException, ParserConfigurationException, SAXException{        
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        System.out.print(cadena);
        Document doc = dbf.newDocumentBuilder().parse(new InputSource(new StringReader(cadena)));
     
      
        NodeList nl = doc.getElementsByTagName("TOKEN");
        Element el = (Element) nl.item(0);
        String valortoken = el.getFirstChild().getNodeValue();             
        return valortoken;
    }      
}


Firma del XML

El siguiente paso es utilizar el certificado digital para firmar el XML. Esto se logra mediante la clase SignToken, que implementa la firma digital utilizando la API de firmas XML de Java

package setsimulacion;

import java.io.FileInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
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.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.keyinfo.X509Data;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;

public class SignToken {

    public StringWriter signToken(StringWriter objectxml, String pathcertificado, String clave) throws Exception {
        /* CREO LOS ELEMENTOS DE FIRMA */     
        // Create a DOM XMLSignatureFactory that will be used to
        // generate the enveloped signature.
            XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");

        // Create a Reference to the enveloped document (in this case,
        // you are signing the whole document, so a URI of "" signifies
        // that, and also specify the SHA1 digest algorithm and
        // the ENVELOPED Transform.
            Reference ref = fac.newReference
             ("", fac.newDigestMethod(DigestMethod.SHA1, null),
              Collections.singletonList
               (fac.newTransform
                (Transform.ENVELOPED, (TransformParameterSpec) null)),
                 null, null);

            // Create the SignedInfo.
            SignedInfo si = fac.newSignedInfo
             (fac.newCanonicalizationMethod
              (CanonicalizationMethod.INCLUSIVE,
               (C14NMethodParameterSpec) null),
                fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
                 Collections.singletonList(ref));


        /* instancio el certificado digital */
        KeyStore p12 = KeyStore.getInstance("pkcs12");
        p12.load(new FileInputStream(pathcertificado), clave.toCharArray());
        Enumeration e = p12.aliases();
        String alias = (String) e.nextElement();
        System.out.println("Alias certifikata:" + alias);
        KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry) p12.getEntry(alias, new KeyStore.PasswordProtection(clave.toCharArray()));
       
        X509Certificate cert = (X509Certificate) keyEntry.getCertificate();
    
        // Create the KeyInfo containing the X509Data.
        KeyInfoFactory kif = fac.getKeyInfoFactory();
        List x509Content = new ArrayList();
        
        x509Content.add(cert);
        X509Data xd = kif.newX509Data(x509Content);
    
        KeyValue keyValue = kif.newKeyValue(cert.getPublicKey());
        ArrayList item = new ArrayList();
        item.add(keyValue);
        item.add(xd);
        /*
        KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));
*/
         KeyInfo ki = kif.newKeyInfo(item);
        
        /* INSTANCIO EL DOCUMENTO A FIRMAR */
        
       
// Instantiate the document to be signed.
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        Document doc = dbf.newDocumentBuilder().parse(new InputSource(new StringReader(objectxml.toString())));
        

// Create a DOMSignContext and specify the RSA PrivateKey and
// location of the resulting XMLSignature's parent element.
        DOMSignContext dsc = new DOMSignContext
        (keyEntry.getPrivateKey(), doc.getDocumentElement());

// Create the XMLSignature, but don't sign it yet.
        XMLSignature signature = fac.newXMLSignature(si, ki);

// Marshal, generate, and sign the enveloped signature.
   signature.sign(dsc);

// Output the resulting document.

StringWriter writer = new StringWriter();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer trans = tf.newTransformer();
trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
trans.transform(new DOMSource(doc), new StreamResult(writer));
      return writer;
    }

}


Resultado

El resultado de este proceso es un XML firmado digitalmente, el cual se utilizada para realizar la petición http de envío.

Comentarios

Entradas populares de este blog

Configurando Servlets y JSP en Jetty

Firma de un Documento XML con Certificado Digital en Java para Uso Tributario en Chile

RESOLUCION SET BASICO DE FACTURA ELECTRÓNICA SII