Ir al contenido


Foto

Problema con certificado de AFIP


  • Por favor identifícate para responder
2 respuestas en este tema

#1 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 477 mensajes

Escrito 22 marzo 2020 - 06:05

Hola amigos..Estoy desarrollando un sistema para coenctarme al web service de facturacion electronica de AFIP.

Esta es la unidad de Autenticacion, a la cual llamo como parametro "wsfe":


delphi
  1. unit UAutenticacion;
  2.  
  3. interface
  4. uses
  5. XMLIntf, XMLDoc,Winapi.shellAPI,
  6. dialogs,Classes,SOAPHTTPClient,sysutils,DateUtils;
  7.  
  8. type
  9. TLogin = class
  10. private
  11.  
  12. Fservicio: string;
  13. Fsign: string;
  14. Ftoken: string;
  15. Fexpiration: TDateTime;
  16. procedure Setservicio(const Value: string);
  17. procedure Setsign(const Value: string);
  18. procedure Settoken(const Value: string);
  19. procedure Setexpiration(const Value: TDateTime);
  20. public
  21. property expiration:TDateTime read Fexpiration write Setexpiration;
  22. property token:string read Ftoken write Settoken;
  23. property sign:string read Fsign write Setsign;
  24. property servicio:string read Fservicio write Setservicio;
  25.  
  26. destructor destroy;
  27. function guardarXML():IXMLDocument;
  28. function armarsource:string;
  29. function generarCMS(const xml1:IXMLDocument):WideString;
  30. procedure respuestaXML(const s:string);
  31. function expiro():boolean;
  32. class function unicoLogin:TLogin;
  33. procedure actualizarsigntoken;
  34. constructor Loguearse(s:string);
  35.  
  36. end;
  37. var
  38. login:TLogin;
  39. implementation
  40.  
  41. { TLogin }
  42.  
  43. uses tra,IniFiles,Forms, LoginCms1,loginresponse, string64, NuevoTicket;
  44. procedure TLogin.actualizarsigntoken;
  45. var
  46. xml1:IXMLDocument;
  47. cms:string;
  48. begin
  49. xml1:=NewXMLDocument();
  50. xml1:=login.guardarXML();
  51. cms:=login.generarCMS(xml1);
  52. login.respuestaXML(cms);
  53. end;
  54.  
  55.  
  56. function TLogin.armarsource: string;
  57. var
  58. archivoini:TIniFile;
  59. source,o,serialNumber,C,cn:string;
  60. begin
  61. archivoini:=TIniFile.Create(ExtractFilePath(Application.ExeName)+ 'caja.ini');
  62. o:=archivoini.ReadString('EMPRESA','O','');
  63. serialNumber:=archivoini.ReadString('EMPRESA','CUIT','');
  64. cn:=archivoini.ReadString('EMPRESA','cn','');
  65. C:=archivoini.ReadString('EMPRESA','C','');
  66. source:='C='+C+',o='+o+',serialNumber='+serialNumber+',cn='+cn;
  67. Result:=source;
  68. end;
  69.  
  70.  
  71. destructor TLogin.destroy;
  72. begin
  73. inherited;
  74. end;
  75.  
  76. function TLogin.expiro: boolean;
  77. var
  78. time:string;
  79. diferencia:int64;
  80. begin
  81. diferencia:=2;
  82. time:=FormatDateTime('hh:mm:ss',Now);
  83. if (MinuteSpan(StrToDateTime(time),login.expiration)<2) then
  84. begin
  85. result:=True;
  86. end
  87. else
  88. begin
  89. result:=False;
  90. end;
  91. end;
  92.  
  93. function TLogin.generarCMS(const xml1:IXMLDocument):WideString;
  94. var
  95. rutabat,linea:string;
  96. archivoCMS:TStringList;
  97. textocms:WideString;
  98. archivoplano:TextFile;
  99. nombrearchivo:string;
  100. begin
  101.  
  102.  
  103. rutabat:=ExtractFilePath(Application.ExeName)+'certificado\'+'cms.bat';
  104. ShellExecute(0,'open',PWideChar(rutabat),nil, nil,0);
  105.  
  106.  
  107. archivoCMS:=TStringList.Create;
  108.  
  109. archivoCMS.LoadFromFile(ExtractFilePath(Application.ExeName) + 'loginticketrequest.xml.cms');
  110.  
  111.  
  112.  
  113. result:=archivoCMS.Text;
  114.  
  115. end;
  116.  
  117. function TLogin.guardarXML():IXMLDocument;
  118. var
  119. loginticketrequest:IXMLLoginTicketRequestType;
  120. loginheader:IXMLHeaderType;
  121. archivoini:TIniFile;
  122. diai,diaf,horai,time,horaf,horae,expiration:string;
  123. XML1:IXMLDocument;
  124. XMLanterior:IXMLDocument;
  125. startnode,nodohijo,nodoexp:IXMLNode;
  126. begin
  127.  
  128. time:=FormatDateTime('hh:mm:ss',now);
  129.  
  130. //crear archivo xml
  131. XML1 := NewXMLDocument;
  132.  
  133. XML1.FileName:='loginticketrequest.xml';
  134.  
  135.  
  136. loginticketrequest:=NewloginTicketRequest;
  137. //asginar valores para loginticketrequest.xml
  138. // version
  139.  
  140. loginticketrequest.Version:='1.0';
  141. //header
  142. // uniqueid son constantes
  143.  
  144. loginticketrequest.header.UniqueId:=4325399;
  145. //hora del pedido.. es ahora
  146. diai:=FormatDateTime('yyyy-mm-dd',Now);
  147. diaf:=FormatDateTime('yyyy-mm-dd',IncHour(Now,12));
  148. horai:=FormatDateTime('hh:mm:ss',now);
  149. horaf:=FormatDateTime('hh:mm:ss',IncHour(Now,12));
  150. loginticketrequest.header.GenerationTime:=diai+ 'T' + horai + '-03:00';
  151. //hora de finalizacion (1 dia es el maximo);
  152. loginticketrequest.header.ExpirationTime:=diaf+'T'+horaf + '-03:00';
  153.  
  154. //servicio a acceder
  155. loginticketrequest.Service:=Fservicio;
  156. XML1.XML.Add(loginticketrequest.XML);
  157. XML1.Active:=True;
  158.  
  159. XML1.SaveToFile(ExtractFilePath(Application.ExeName) + 'loginticketrequest.xml');
  160.  
  161. Result:=XML1;
  162.  
  163.  
  164.  
  165. end;
  166.  
  167. constructor TLogin.Loguearse(s:string);
  168. var
  169. XML1:IXMLDocument;
  170. CMS:WideString;
  171. CMS64:WideString;
  172. TRA:TXMLDocument;
  173. begin
  174. self.Fservicio:=s;
  175. XML1:=self.guardarXML;
  176. CMS:=self.generarCMS(XML1);
  177. ShowMessage(CMS);
  178. CMS64:=Base64Encode(CMS);
  179. ShowMessage(CMS64);
  180. self.respuestaXML(CMS64);
  181. end;
  182.  
  183. procedure TLogin.respuestaXML(const s:string);
  184. var
  185. RIOLogin:THTTPRIO;
  186. xml3:IXMLDocument;
  187. content,expira,nodo:string;
  188. nodohijo,nodohijosign,startnode,startnodesign:IXMLNode;
  189. begin
  190.  
  191. RIOLogin:=THTTPRIO.Create(nil);
  192. with RIOLogin do
  193. begin
  194. WSDLLocation:='https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl';
  195. Port:='LoginCms';
  196. Service:='LoginCMSService';
  197.  
  198. end;
  199.  
  200.  
  201. xml3:=NewXMLDocument;
  202. content:=(RIOLogin as LoginCms).loginCms(s);
  203. xml3.XML.Text:=content;
  204. xml3.Active:=True;
  205. xml3.SaveToFile(ExtractFilePath(Application.ExeName) + 'respuestaxml.xml');
  206. startnode:=xml3.ChildNodes[1];
  207. nodohijo:=startnode.ChildNodes[0];
  208. nodo:=nodohijo.ChildNodes['expirationTime'].Text;
  209. expira:=Copy(nodo,12,8);
  210. Setexpiration(StrToDateTime(expira));
  211. //setear sign y token
  212. startNodesign := xml3.ChildNodes[1];
  213. nodohijosign:=startnodesign.ChildNodes[1];
  214.  
  215.  
  216. Setsign(nodohijosign.ChildNodes['sign'].Text);
  217.  
  218. Settoken(nodohijosign.ChildNodes['token'].Text);
  219.  
  220. end;
  221.  
  222.  
  223.  
  224. procedure TLogin.Setexpiration(const Value: TDateTime);
  225. begin
  226. Fexpiration := Value;
  227. end;
  228.  
  229. procedure TLogin.Setservicio(const Value: string);
  230. begin
  231. Fservicio := Value;
  232. end;
  233.  
  234. procedure TLogin.Setsign(const Value: string);
  235. begin
  236. Fsign := Value;
  237. end;
  238.  
  239.  
  240. procedure TLogin.Settoken(const Value: string);
  241. begin
  242. Ftoken := Value;
  243. end;
  244.  
  245.  
  246. class function TLogin.unicoLogin:TLogin;
  247.  
  248. begin
  249. if login<>nil then
  250. begin
  251. if login.expiro=False then
  252. begin
  253. Result:=login;
  254. end
  255. else
  256. begin
  257. login.actualizarsigntoken;
  258. Result:=login;
  259. end;
  260. end
  261. else
  262. begin
  263. login:=Tlogin.Loguearse('padron-puc-ws-consulta-nivel3');
  264. login.respuestaXML(login.generarCMS(login.guardarXML()));
  265.  
  266. end;
  267. end;
  268.  
  269. end. 

Y obtengo No se puede decodificar el BASE64.

 

Como veran en el constructor sigo los pasos de las especificaciones tecnicas de AFIP:

 

1- Creo el xml con tiempo de expiracion

2-Genero el CMS en un archivo, para lo mismo ejecuto un archivo BAT (Codigo DOS digamos):

 

c:\OpenSSL-Win32\bin\openssl.exe cms -sign -in G:\despensa\Win32\Debug\LoginTicketRequest.xml -out G:\despensa\Win32\Debug\LoginTicketRequest.xml.cms -signer G:\despensa\Win32\Debug\certificado\certificado.crt -inkey G:\despensa\Win32\Debug\certificado\caruso12021991.key -nodetach -outform PEM
 
y tambien podran ver que puse dos ShowMessage y segun veo el problema es que me carga el archivo LoginTicketRequest.xml.cms anterior, es decir lo carga y luego se regenera, ese es uno de los problemas principales.
Lo cual no entiendo porque sucede si el codigo del archivo BAT esta antes que la creacion del StringList.
 
Y otra duda es que si mal no recuerdo hay que extraer el "-----BEGIN CMS-----" y el "-----END CMS-----" antes de codificar en BASE64, segun los requerimientos de AFIP.
 
Espero puedan ayudarme

  • 0

#2 giulichajari

giulichajari

    Advanced Member

  • Miembros
  • PipPipPip
  • 477 mensajes

Escrito 22 marzo 2020 - 01:09

Lo que me parece incorrecto es que en la unidad donde encodeo el cms a base 64 asi como las variables en el constructor son de tipo string, este mismo admite solo 256 caracteres, cuando el texto es mas largo:


delphi
  1. unit string64;
  2.  
  3. interface
  4.  
  5. uses
  6. Winapi.Windows,Winapi.Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  7. Dialogs, StdCtrls;
  8.  
  9. const
  10. B64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
  11.  
  12. function Base64Decode(const S: string): string;
  13. function Base64Encode(const S: String): String;
  14.  
  15. implementation
  16.  
  17.  
  18. function Base64Encode(const S: String): String;
  19. var
  20. InBuf: array[0..2] of Byte;
  21. OutBuf: array[0..3] of Char;
  22. iI, iJ: Integer;
  23. begin
  24. SetLength(Result, ((Length(S) + 2) div 3) * 4);
  25. for iI := 1 to ((Length(S) + 2) div 3) do
  26. begin
  27. if Length(S) < (iI * 3) then
  28. Move(S[(iI - 1) * 3 + 1], InBuf, Length(S) - (iI - 1) * 3)
  29. else
  30. Move(S[(iI - 1) * 3 + 1], InBuf, 3);
  31. OutBuf[0] := B64Table[((InBuf[0] and $FC) shr 2) + 1];
  32. OutBuf[1] := B64Table[(((InBuf[0] and $3) shl 4) or ((InBuf[1] and $F0) shr 4)) + 1];
  33. OutBuf[2] := B64Table[(((InBuf[1] and $F) shl 2) or ((InBuf[2] and $C0) shr 6)) + 1];
  34. OutBuf[3] := B64Table[(InBuf[2] and $3F) + 1];
  35. Move(OutBuf, Result[(iI - 1) * 4 + 1], 4);
  36. end;
  37. if Length(S) mod 3 = 1 then
  38. begin
  39. Result[Length(Result) - 1] := '=';
  40. Result[Length(Result)] := '=';
  41. end
  42. else if Length(S) mod 3 = 2 then
  43. Result[Length(Result)] := '=';
  44. end;
  45.  
  46. function Base64Decode(const S: string): string;
  47. var
  48. OutBuf: array[0..2] of Byte;
  49. InBuf: array[0..3] of Byte;
  50. iI, iJ: Integer;
  51. begin
  52. if Length(S) mod 4 <> 0 then
  53. raise Exception.Create('Base64: Incorrect string format');
  54. SetLength(Result, ((Length(S) div 4) - 1) * 3);
  55. for iI := 1 to (Length(S) div 4) - 1 do
  56. begin
  57. Move(S[(iI - 1) * 4 + 1], InBuf, 4);
  58. for iJ := 0 to 3 do
  59. case InBuf[iJ] of
  60. 43: InBuf[iJ] := 62;
  61. 48..57: Inc(InBuf[iJ], 4);
  62. 65..90: Dec(InBuf[iJ], 65);
  63. 97..122: Dec(InBuf[iJ], 71);
  64. else
  65. InBuf[iJ] := 63;
  66. end;
  67. OutBuf[0] := (InBuf[0] shl 2) or ((InBuf[1] shr 4) and $3);
  68. OutBuf[1] := (InBuf[1] shl 4) or ((InBuf[2] shr 2) and $F);
  69. OutBuf[2] := (InBuf[2] shl 6) or (InBuf[3] and $3F);
  70. Move(OutBuf, Result[(iI - 1) * 3 + 1], 3);
  71. end;
  72. if Length(S) <> 0 then
  73. begin
  74. Move(S[Length(S) - 3], InBuf, 4);
  75. if InBuf[2] = 61 then
  76. begin
  77. for iJ := 0 to 1 do
  78. case InBuf[iJ] of
  79. 43: InBuf[iJ] := 62;
  80. 48..57: Inc(InBuf[iJ], 4);
  81. 65..90: Dec(InBuf[iJ], 65);
  82. 97..122: Dec(InBuf[iJ], 71);
  83. else
  84. InBuf[iJ] := 63;
  85. end;
  86. OutBuf[0] := (InBuf[0] shl 2) or ((InBuf[1] shr 4) and $3);
  87. Result := Result + Char(OutBuf[0]);
  88. end
  89. else if InBuf[3] = 61 then
  90. begin
  91. for iJ := 0 to 2 do
  92. case InBuf[iJ] of
  93. 43: InBuf[iJ] := 62;
  94. 48..57: Inc(InBuf[iJ], 4);
  95. 65..90: Dec(InBuf[iJ], 65);
  96. 97..122: Dec(InBuf[iJ], 71);
  97. else
  98. InBuf[iJ] := 63;
  99. end;
  100. OutBuf[0] := (InBuf[0] shl 2) or ((InBuf[1] shr 4) and $3);
  101. OutBuf[1] := (InBuf[1] shl 4) or ((InBuf[2] shr 2) and $F);
  102. Result := Result + Char(OutBuf[0]) + Char(OutBuf[1]);
  103. end
  104. else
  105. begin
  106. for iJ := 0 to 3 do
  107. case InBuf[iJ] of
  108. 43: InBuf[iJ] := 62;
  109. 48..57: Inc(InBuf[iJ], 4);
  110. 65..90: Dec(InBuf[iJ], 65);
  111. 97..122: Dec(InBuf[iJ], 71);
  112. else
  113. InBuf[iJ] := 63;
  114. end;
  115. OutBuf[0] := (InBuf[0] shl 2) or ((InBuf[1] shr 4) and $3);
  116. OutBuf[1] := (InBuf[1] shl 4) or ((InBuf[2] shr 2) and $F);
  117. OutBuf[2] := (InBuf[2] shl 6) or (InBuf[3] and $3F);
  118. Result := Result + Char(OutBuf[0]) + Char(OutBuf[1]) + Char(OutBuf[2]);
  119. end;
  120. end;
  121. end;
  122. end.

No deberia ser WideString?

 

Por otro lado he probado el .key y el .crt con un aplicativo que genera el ticket de acceso y lo genera perfectamente, por lo que esos archivos estan bien


  • 0

#3 balger

balger

    Member

  • Miembros
  • PipPip
  • 17 mensajes

Escrito 14 febrero 2021 - 10:17

Hola giulichajari

Bueno ya paso un año desde tu consulta. Espero que le sirva a alguien mas. Las respuesta es que estas codificando en base64, cuando tenés que generar un certificado en base64. En Delphi no podes generar el certificado. Para eso tenés a usar OpenSSL.

Copia tal cual la instrucción del manual y ejecutala. eso generará tu certificado, Este contiene 2 lineas extras de comentario, la 1ra y la última, que tenes que eliminar.


  • 0




IP.Board spam blocked by CleanTalk.