Buscar en este blog

viernes, 24 de mayo de 2013

Cómo firmar el DTE

Cómo se firma el DTE


Bueno en este tema existen muchos que pueden dar una opinión acerca de cómo realizar esta tarea, sin embargo la experiencia me ha demostrado que el problema no es firmar el documento sino más bien que es lo que debemos firmar. Muchas personas me dicen que firman sus documentos DTE pero al momento de enviarlos al SII este les regresa que la firma es inválida. Expondré aquí la forma que utilizo personalmente para realizar el proceso, sin perjuicio que existan otros desarrolladores que utilicen una forma mas elegante.

Consideraciones generales

Una de las primeras cosas que realizo al momento de intentar firmar el documento DTE es ordenar la información a firmar. Los desarrolladores con más experiencia en este tema saben que al construir un documento XML este no es necesariamente el mismo cuando uno lo visualiza en otros visualizadores de documentos XML. Bueno regresando a lo nuestro, primero ordenemos el DTE

Identación



Si Ud. Cuenta con un documento DTE con estas características lo primero que debería hacer es quitar la identación del documento. Esto tiene una razón, puesto que el CAF entregado por el SII no trae identación es preferible dejar todo el documento alineado según el CAF. Entonces al momento de calcular la firma sobre el CAF no generará errores de timbre. Esta es mi opinión, no exenta de comentarios y observaciones.



















Deje el documento DTE de esta forma.


















CAF

Una vez ordenado el documento DTE, es necesario agregar el CAF al documento, para esto es necesario tomar el nodo CAF desde el archivo de autorización de folios del SII. Cuando digo tomar, es rescatar el nodo sin realizar ninguna modificación o identación. El nodo CAF debe ser insertado tal cual en el documento DTE. Si se modifica de alguna forma el cálculo del timbre fallará.

Entonces segun esto el DTE debería quedar como sigue:




Posterior a esto se encuentra la generación del timbre del TED, cuya información se encuentra disponible en la sección Ejemplo Timbre Electrónico del DTE . Cuando genere el timbre del TED su DTE debería tener esta forma:















Aplicar Firma al DTE


Bueno ya tiene preparado su DTE para ser firmado. Ahora debemos aplicar la siguiente rutina de firma al DTE expuesto. Antes debo señalar que al momento de abrir el documento XML en .Net utilice la propiedad PreserveWhiteSpace = true del objeto XmlDocument. Esto logra abrir el DTE sin ordenar los nodos o identarlo. Si no lo hace de esta forma el timbre que calculo se invalidará.

Una vez abierto el DTE (xml) puede aplicar esta rutina para su firmado:




#region FIRMA DTE

public static void firmarDocumentoXml(ref XmlDocument xmldocument, X509Certificate2 certificado, string referenciaUri )
{
 ////
 //// Cree el objeto SignedXml donde xmldocument
 //// representa el documento DTE preparado para
 //// ser firmado. Recuerde que debe ser abierto 
 //// con la propiedad PreserveWhiteSpace = true
 SignedXml signedXml = new SignedXml(xmldocument);

 ////
 //// Agregue la clave privada al objeto signedXml
 signedXml.SigningKey = certificado.PrivateKey;

 ////
 //// Recupere el objeto signature desde signedXml
 Signature XMLSignature = signedXml.Signature;

 ////
 //// Cree la refrerencia al documento DTE
 //// recuerde que la referencia tiene el 
 //// formato '#reference'
 //// ejemplo '#DTE001'
 Reference reference = new Reference();
 reference.Uri = referenciaUri;

 ////
 //// Agregue la referencia al objeto signature
 XMLSignature.SignedInfo.AddReference(reference);
 KeyInfo keyInfo = new KeyInfo();
 keyInfo.AddClause(new RSAKeyValue((RSA)certificado.PrivateKey));

 ////
 //// Agregar información del certificado x509
 keyInfo.AddClause(new KeyInfoX509Data(certificado));
 XMLSignature.KeyInfo = keyInfo;

 ////
 //// Calcule la firma y recupere la representacion
 //// de la firma en un objeto xmlElement
 signedXml.ComputeSignature();
 XmlElement xmlDigitalSignature = signedXml.GetXml();

 ////
 //// Inserte la firma en el documento DTE
 xmldocument.DocumentElement.AppendChild(xmldocument.ImportNode(xmlDigitalSignature, true));

}



#endregion

Para ser honesto esta rutina es capaz de firmar el DTE y El SETDTE solo se debe pasar la referencia adecuada para indicar que nodo debe firmar.


Bueno espero que esta breve rutina les permita avanzar en sus proyectos. Como siempre los invito a dejar un comentario acerca de como mejorar estas rutinas.



miércoles, 22 de mayo de 2013

Cómo enviar un Documento Electronico al SII

Cómo enviar un DTE o Libro Compra Venta ( IECV ) al SII


Bueno ahora expongo codigo para realizar el envío de un documento electrónico al SII.

Antecedentes necesarios:


Nota: Documento = DTE, IECV, Etc

  • Rut del emisor del documento.
  • Rut de la empresa que genera el documento.
  • Nombre del documento a enviar.
  • FullPath del archivo a procesar.
  • Token del SII en el ambiente que corresponde.

Librerias necesarias:


using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Diagnostics;
using System.Security.Permissions;
using System.Net;


Secuencia de preparacion del documento electrónico a enviar al SII

   #region PREPARACION

   ////
   //// Prepare  los paramentros para utilizarlos
   //// en el envio del documento.
   rutEmisor     = rutEmisor.Replace("-", string.Empty);
   rutEmpresa    = rutEmpresa.Replace("-", string.Empty);

   ////
   //// Recupere el cuerpo y digito verificador de los 
   //// rut involucrados.
   string pRutEmisor   = rutEmisor.Substring(0, (rutEmisor.Length - 1));
   string pDigEmisor   = rutEmisor.Substring(rutEmisor.Length - 1); 
   string pRutEmpresa  = rutEmpresa.Substring(0, (rutEmpresa.Length - 1));
   string pDigEmpresa  = rutEmpresa.Substring(rutEmpresa.Length - 1);

   ////
   //// Cree el header del request a enviar al SII
   //// segun la información solicitada del SII
   secuencia.Append("--7d23e2a11301c4\r\n");
   secuencia.Append("Content-Disposition: form-data; name=\"rutSender\"\r\n");
   secuencia.Append("\r\n");
   secuencia.Append(pRutEmisor + "\r\n");
   secuencia.Append("--7d23e2a11301c4\r\n");
   secuencia.Append("Content-Disposition: form-data; name=\"dvSender\"\r\n");
   secuencia.Append("\r\n");
   secuencia.Append(pDigEmisor + "\r\n");
   secuencia.Append("--7d23e2a11301c4\r\n");
   secuencia.Append("Content-Disposition: form-data; name=\"rutCompany\"\r\n");
   secuencia.Append("\r\n");
   secuencia.Append(pRutEmpresa + "\r\n");
   secuencia.Append("--7d23e2a11301c4\r\n");
   secuencia.Append("Content-Disposition: form-data; name=\"dvCompany\"\r\n");
   secuencia.Append("\r\n");
   secuencia.Append(pDigEmpresa + "\r\n");
   secuencia.Append("--7d23e2a11301c4\r\n");
   secuencia.Append("Content-Disposition: form-data; name=\"archivo\"; filename=\"" + nombreArchivo + "\"\r\n");
   secuencia.Append("Content-Type: text/xml\r\n");
   secuencia.Append("\r\n");
   
   ////
   //// Lea el documento xml que se va a enviar al SII
   XDocument xdocument = XDocument.Load(uri, LoadOptions.PreserveWhitespace);
   
   ////
   //// Cargue el documento en el objeto secuencia
   secuencia.Append("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\r");
   secuencia.Append(xdocument.ToString(SaveOptions.DisableFormatting));
   secuencia.Append("\r\n");

   #endregion



Ahora es necesario configurar el Request que sera enviado al SII

   ////
   //// Aqui se configura el request que hace la solicitud al SII
   #region CONFIGURACION DE REQUEST
   
   ////
   //// Defina que ambiente utilizar.
   //// Certificacion "https://maullin.sii.cl/cgi_dte/UPL/DTEUpload";
   string pUrl = "https://maullin.sii.cl/cgi_dte/UPL/DTEUpload";
   
   ////
   //// Cree los parametros del header.
   //// Token debe ser el valor asignado por el SII
   string pMethod = "POST";
   string pAccept = "image/gif, image/x-xbitmap, image/jpeg, image/pjpeg,application/vnd.ms-powerpoint, application/ms-excel,application/msword, */*";
   string pReferer = "www.hefestosDte.cl";
   string pToken = "TOKEN={0}";
   
   ////
   //// Cree un nuevo request para iniciar el proceso
   HttpWebRequest request = (HttpWebRequest)WebRequest.Create(pUrl);
   request.Method = pMethod;
   request.Accept = pAccept;
   request.Referer = pReferer;
   
   ////
   //// Agregar el content-type
   request.ContentType = "multipart/form-data: boundary=7d23e2a11301c4";
   request.ContentLength = secuencia.Length;
   
   ////
   //// Defina manualmente los headers del request
   request.Headers.Add("Accept-Language", "es-cl");
   request.Headers.Add("Accept-Encoding", "gzip, deflate");
   request.Headers.Add("Cache-Control", "no-cache");
   request.Headers.Add("Cookie", string.Format(pToken, token));
   
   ////
   //// Defina el user agent
   request.UserAgent = "Mozilla/4.0 (compatible; PROG 1.0; Windows NT 5.0; YComp 5.0.2.4)";
   request.KeepAlive = true;
   
   #endregion


   ////
   //// Escritura del request
   #region ESCRIBE LA DATA NECESARIA
   
   ////
   //// Recupere el streamwriter para escribir la secuencia
   try
   {
   
    using (StreamWriter sw = new StreamWriter(request.GetRequestStream()))
    {
     sw.Write(secuencia.ToString());
    }
   
   }
   catch (Exception ex)
   {
     ////
     //// Error en el metodo
     //// Error del formato del envio

   
   }
   
   #endregion



Ahora puede realizar el envío al SII utilizando esta rutina

   ////
   //// Enviar libro/dte y solicitar la respuesta del SII
   #region ENVIA Y SOLICITA RESPUESTA
   
   try
   {
   
    ////
    //// Defina donde depositar el resultado
    string respuestaSii = string.Empty;
   
    ////
    //// Recupere la respuesta del sii
    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
         using (StreamReader sr = new StreamReader(response.GetResponseStream()))
        { 
         respuestaSii = sr.ReadToEnd().Trim();
        }
   
    }
   
    ////
    //// Hay respuesta?
    if (string.IsNullOrEmpty(respuestaSii))
         throw new ArgumentNullException("Respuesta del SII es null");
   
   
    ////
    //// Interprete la respuesta del SII.
    //// respuestaSii contiene la respuesta del SII acerca del envio en formato XML

   
   }
   catch (Exception ex)
   {
    
     ////
     //// Error en el metodo
     //// No fue posible enviar o recepcionar la respuesta del SII
    
    
   }
   
   #endregion





Bueno sé que el ejemplo es muy simpe pero sé que les servirá, pronto agregaré los comentarios para hacerlo mas comprensible.

lunes, 20 de mayo de 2013

Ejemplo Timbre Electrónico del DTE


Ejemplo Timbre Electrónico del DTE


Para aquellos que aun no pueden generar el timbre electrónico sobre su DTE, les voy a enseñar a realizarlo con las herramientas de C#. Muchos de nosotros cuando iniciamos en este tema recurríamos a clases externas para poder realizar esta técnica. Pues no teníamos el conocimiento ni los ejemplos para hacerlo. Bueno con el paso del tiempo y la mejora en la comprensión de los problemas criptográficos he podido factorizar el código para el Timbre del Documento DTE.


Ejemplo del SII

El SII en su documento Manual de Certificación
Titulo : TIMBRE ELECTRONICO DE EJEMPLO
pagina:  25
Del XML indicado se arma el string que será firmado (Mensaje)

Mensaje=
<DD><RE>97975000-5</RE><TD>33</TD><F>27</F><FE>2003-09-08</FE>
<RR>8414240-9</RR><RSR>JORGE GONZALEZ LTDA</RSR><MNT>502946</M
NT><IT1>Cajon AFECTO</IT1><CAF version="1.0"><DA><RE>97975000-
5</RE><RS>RUT DE PRUEBA</RS><TD>33</TD><RNG><D>1</D><H>200</H>
</RNG><FA>2003-09-04</FA><RSAPK><M>0a4O6Kbx8Qj3K4iWSP4w7KneZYe
J+g/prihYtIEolKt3cykSxl1zO8vSXu397QhTmsX7SBEudTUx++2zDXBhZw==<
/M><E>Aw==</E></RSAPK><IDK>100</IDK></DA><FRMA algoritmo="SHA1
withRSA">g1AQX0sy8NJugX52k2hTJEZAE9Cuul6pqYBdFxj1N17umW7zG/hAa
vCALKByHzdYAfZ3LhGTXCai5zNxOo4lDQ==</FRMA></CAF><TSTED>2003-09
-08T12:28:31</TSTED></DD>



Desde el Código de Autorización de Folios (CAF) el SII entrega la llave privada en
formato PEM con la cual se firmará el Mensaje.

-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANGuDuim8fEI9yuIlkj+MOyp3mWHifoP6a4oWLSBKJSrd3MpEsZd
czvL0l7t/e0IU5rF+0gRLnU1Mfvtsw1wYWcCAQMCQQCLyV9FxKFLW09yWw7bVCCd
xpRDr7FRX/EexZB4VhsNxm/vtJfDZyYle0Lfy42LlcsXxPm1w6Q6NnjuW+AeBy67
AiEA7iMi5q5xjswqq+49RP55o//jqdZL/pC9rdnUKxsNRMMCIQDhaHdIctErN2hC
IP9knS3+9zra4R+5jSXOvI+3xVhWjQIhAJ7CF0R0S7SIHHKe04NUURf/7RvkMqm1
08k74sdnXi3XAiEAlkWk2vc2HM+a1sCqQxNz/098ketqe7NuidMKeoOQObMCIQCk
FAMS9IcPcMjk7zI2r/4EEW63PSXyN7MFAX7TYe25mw==
-----END RSA PRIVATE KEY-----

El resultado obtenido de la firma RSA-SHA1, utilizado la llave privada sobre el
mensaje es:

pqjXHHQLJmyFPMRvxScN7tYHvIsty0pqL2LLYaG43jMmnfiZfllLA0wb32lP+HBJ
/tf8nziSeorvjlx410ZImw==

El presente ejemplo muestra con claridad los elementos que deben utilizarse para generar el timbre. Ahora traspasaremos esta información al formato C#.

//////////////////////////////////////////////////////////////////////
//// BY: Marcelo Rojas R.
//// Dt: 16-05-2013
//// El ejercicio actual representa un ejemplo del SII
//// Donde se suministra el valor del nodo TED, es decir 
//// su contenido y posteriormente se calcula el timbre
//////////////////////////////////////////////////////////////////////
public static void PruebaTimbreDD()
{

   ////
   //// Contenido del nodo TED del ejemplo. 
   //// Este es el formato que debe tener los datos
   //// 
   string DD = string.Empty;
   DD += "<DD><RE>97975000-5</RE><TD>33</TD><F>27</F><FE>2003-09-08</FE>";
   DD += "<RR>8414240-9</RR><RSR>JORGE GONZALEZ LTDA</RSR><MNT>502946</M";
   DD += "NT><IT1>Cajon AFECTO</IT1><CAF version=\"1.0\"><DA><RE>97975000-";
   DD += "5</RE><RS>RUT DE PRUEBA</RS><TD>33</TD><RNG><D>1</D><H>200</H>";
   DD += "</RNG><FA>2003-09-04</FA><RSAPK><M>0a4O6Kbx8Qj3K4iWSP4w7KneZYe";
   DD += "J+g/prihYtIEolKt3cykSxl1zO8vSXu397QhTmsX7SBEudTUx++2zDXBhZw==<";
   DD += "/M><E>Aw==</E></RSAPK><IDK>100</IDK></DA><FRMA algoritmo=\"SHA1";
   DD += "withRSA\">g1AQX0sy8NJugX52k2hTJEZAE9Cuul6pqYBdFxj1N17umW7zG/hAa";
   DD += "vCALKByHzdYAfZ3LhGTXCai5zNxOo4lDQ==</FRMA></CAF><TSTED>2003-09";
   DD += "-08T12:28:31</TSTED></DD>";    

   ////
   //// Representa la clave privada rescatada desde el CAF que envía el SII
   //// para la prueba propuesta por ellos.
   ////
   string pk = string.Empty;
   pk += "MIIBOwIBAAJBANGuDuim8fEI9yuIlkj+MOyp3mWHifoP6a4oWLSBKJSrd3MpEsZd";
   pk += "czvL0l7t/e0IU5rF+0gRLnU1Mfvtsw1wYWcCAQMCQQCLyV9FxKFLW09yWw7bVCCd";
   pk += "xpRDr7FRX/EexZB4VhsNxm/vtJfDZyYle0Lfy42LlcsXxPm1w6Q6NnjuW+AeBy67";
   pk += "AiEA7iMi5q5xjswqq+49RP55o//jqdZL/pC9rdnUKxsNRMMCIQDhaHdIctErN2hC";
   pk += "IP9knS3+9zra4R+5jSXOvI+3xVhWjQIhAJ7CF0R0S7SIHHKe04NUURf/7RvkMqm1";
   pk += "08k74sdnXi3XAiEAlkWk2vc2HM+a1sCqQxNz/098ketqe7NuidMKeoOQObMCIQCk";
   pk += "FAMS9IcPcMjk7zI2r/4EEW63PSXyN7MFAX7TYe25mw==";
   

   //// 
   //// Este es el resultado que el SII indica debe obtenerse despues de crear
   //// el timbre sobre los datos expuestos.
   ////
   const string HTIMBRE = "pqjXHHQLJmyFPMRvxScN7tYHvIsty0pqL2LLYaG43jMmnfiZfllLA0wb32lP+HBJ/tf8nziSeorvjlx410ZImw==";

   
   //// //////////////////////////////////////////////////////////////////
   //// Generar timbre sobre los datos del tag DD utilizando la clave 
   //// privada suministrada por el SII en el archivo CAF
   //// //////////////////////////////////////////////////////////////////

   ////
   //// Calcule el hash de los datos a firmar DD
   //// transformando la cadena DD a arreglo de bytes, luego con
   //// el objeto 'SHA1CryptoServiceProvider' creamos el Hash del
   //// arreglo de bytes que representa los datos del DD
   ASCIIEncoding ByteConverter = new ASCIIEncoding();
   byte[] bytesStrDD = ByteConverter.GetBytes(DD);
   byte[] HashValue = new SHA1CryptoServiceProvider().ComputeHash(bytesStrDD);

   ////
   //// Cree el objeto Rsa para poder firmar el hashValue creado
   //// en el punto anterior. La clase FuncionesComunes.crearRsaDesdePEM()
   //// Transforma la llave rivada del CAF en formato PEM a el objeto
   //// Rsa necesario para la firma.
   RSACryptoServiceProvider rsa = FuncionesComunes.crearRsaDesdePEM(pk);

   ////
   //// Firme el HashValue ( arreglo de bytes representativo de DD )
   //// utilizando el formato de firma SHA1, lo cual regresará un nuevo 
   //// arreglo de bytes.
   byte[] bytesSing = rsa.SignHash(HashValue, "SHA1");

   ////
   //// Recupere la representación en base 64 de la firma, es decir de
   //// el arreglo de bytes 
   string FRMT1 = Convert.ToBase64String(bytesSing);

   ////
   //// Comprobación del timbre generado por nuestra rutina contra el
   //// valor 
   if (HTIMBRE.Equals(FRMT1))
   {
      Console.WriteLine("Comprobacion OK");
   }
   else
   {
      Console.WriteLine("Comprobacion NOK");
   }

}







Descargue aqui la clase que contiene el metodo de FuncionesComunes.crearRsaDesdePEM(pk) la cual permite transformar una clave privada en formato PEM a formato RSA.


Bueno espero que este codigo los ayude, especialmente a aquellos que recien comienzan con este tema.