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":
Código Delphi
[-]unit UAutenticacion;
interface
uses
XMLIntf, XMLDoc,Winapi.shellAPI,
dialogs,Classes,SOAPHTTPClient,sysutils,DateUtils;
type
TLogin = class
private
Fservicio: string;
Fsign: string;
Ftoken: string;
Fexpiration: TDateTime;
procedure Setservicio(const Value: string);
procedure Setsign(const Value: string);
procedure Settoken(const Value: string);
procedure Setexpiration(const Value: TDateTime);
public
property expiration:TDateTime read Fexpiration write Setexpiration;
property token:string read Ftoken write Settoken;
property sign:string read Fsign write Setsign;
property servicio:string read Fservicio write Setservicio;
destructor destroy;
function guardarXML():IXMLDocument;
function armarsource:string;
function generarCMS(const xml1:IXMLDocument):WideString;
procedure respuestaXML(const s:string);
function expiro():boolean;
class function unicoLogin:TLogin;
procedure actualizarsigntoken;
constructor Loguearse(s:string);
end;
var
login:TLogin;
implementation
uses tra,IniFiles,Forms, LoginCms1,loginresponse, string64, NuevoTicket;
procedure TLogin.actualizarsigntoken;
var
xml1:IXMLDocument;
cms:string;
begin
xml1:=NewXMLDocument();
xml1:=login.guardarXML();
cms:=login.generarCMS(xml1);
login.respuestaXML(cms);
end;
function TLogin.armarsource: string;
var
archivoini:TIniFile;
source,o,serialNumber,C,cn:string;
begin
archivoini:=TIniFile.Create(ExtractFilePath(Application.ExeName)+ 'caja.ini');
o:=archivoini.ReadString('EMPRESA','O','');
serialNumber:=archivoini.ReadString('EMPRESA','CUIT','');
cn:=archivoini.ReadString('EMPRESA','cn','');
C:=archivoini.ReadString('EMPRESA','C','');
source:='C='+C+',o='+o+',serialNumber='+serialNumber+',cn='+cn;
Result:=source;
end;
destructor TLogin.destroy;
begin
inherited;
end;
function TLogin.expiro: boolean;
var
time:string;
diferencia:int64;
begin
diferencia:=2;
time:=FormatDateTime('hh:mm:ss',Now);
if (MinuteSpan(StrToDateTime(time),login.expiration)<2) then
begin
result:=True;
end
else
begin
result:=False;
end;
end;
function TLogin.generarCMS(const xml1:IXMLDocument):WideString;
var
rutabat,linea:string;
archivoCMS:TStringList;
textocms:WideString;
archivoplano:TextFile;
nombrearchivo:string;
begin
rutabat:=ExtractFilePath(Application.ExeName)+'certificado\'+'cms.bat';
ShellExecute(0,'open',PWideChar(rutabat),nil, nil,0);
archivoCMS:=TStringList.Create;
archivoCMS.LoadFromFile(ExtractFilePath(Application.ExeName) + 'loginticketrequest.xml.cms');
result:=archivoCMS.Text;
end;
function TLogin.guardarXML():IXMLDocument;
var
loginticketrequest:IXMLLoginTicketRequestType;
loginheader:IXMLHeaderType;
archivoini:TIniFile;
diai,diaf,horai,time,horaf,horae,expiration:string;
XML1:IXMLDocument;
XMLanterior:IXMLDocument;
startnode,nodohijo,nodoexp:IXMLNode;
begin
time:=FormatDateTime('hh:mm:ss',now);
XML1 := NewXMLDocument;
XML1.FileName:='loginticketrequest.xml';
loginticketrequest:=NewloginTicketRequest;
loginticketrequest.Version:='1.0';
loginticketrequest.header.UniqueId:=4325399;
diai:=FormatDateTime('yyyy-mm-dd',Now);
diaf:=FormatDateTime('yyyy-mm-dd',IncHour(Now,12));
horai:=FormatDateTime('hh:mm:ss',now);
horaf:=FormatDateTime('hh:mm:ss',IncHour(Now,12));
loginticketrequest.header.GenerationTime:=diai+ 'T' + horai + '-03:00';
loginticketrequest.header.ExpirationTime:=diaf+'T'+horaf + '-03:00';
loginticketrequest.Service:=Fservicio;
XML1.XML.Add(loginticketrequest.XML);
XML1.Active:=True;
XML1.SaveToFile(ExtractFilePath(Application.ExeName) + 'loginticketrequest.xml');
Result:=XML1;
end;
constructor TLogin.Loguearse(s:string);
var
XML1:IXMLDocument;
CMS:WideString;
CMS64:WideString;
TRA:TXMLDocument;
begin
self.Fservicio:=s;
XML1:=self.guardarXML;
CMS:=self.generarCMS(XML1);
ShowMessage(CMS);
CMS64:=Base64Encode(CMS);
ShowMessage(CMS64);
self.respuestaXML(CMS64);
end;
procedure TLogin.respuestaXML(const s:string);
var
RIOLogin:THTTPRIO;
xml3:IXMLDocument;
content,expira,nodo:string;
nodohijo,nodohijosign,startnode,startnodesign:IXMLNode;
begin
RIOLogin:=THTTPRIO.Create(nil);
with RIOLogin do
begin
WSDLLocation:='https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl';
Port:='LoginCms';
Service:='LoginCMSService';
end;
xml3:=NewXMLDocument;
content:=(RIOLogin as LoginCms).loginCms(s);
xml3.XML.Text:=content;
xml3.Active:=True;
xml3.SaveToFile(ExtractFilePath(Application.ExeName) + 'respuestaxml.xml');
startnode:=xml3.ChildNodes[1];
nodohijo:=startnode.ChildNodes[0];
nodo:=nodohijo.ChildNodes['expirationTime'].Text;
expira:=Copy(nodo,12,8);
Setexpiration(StrToDateTime(expira));
startNodesign := xml3.ChildNodes[1];
nodohijosign:=startnodesign.ChildNodes[1];
Setsign(nodohijosign.ChildNodes['sign'].Text);
Settoken(nodohijosign.ChildNodes['token'].Text);
end;
procedure TLogin.Setexpiration(const Value: TDateTime);
begin
Fexpiration := Value;
end;
procedure TLogin.Setservicio(const Value: string);
begin
Fservicio := Value;
end;
procedure TLogin.Setsign(const Value: string);
begin
Fsign := Value;
end;
procedure TLogin.Settoken(const Value: string);
begin
Ftoken := Value;
end;
class function TLogin.unicoLogin:TLogin;
begin
if login<>nil then
begin
if login.expiro=False then
begin
Result:=login;
end
else
begin
login.actualizarsigntoken;
Result:=login;
end;
end
else
begin
login:=Tlogin.Loguearse('padron-puc-ws-consulta-nivel3');
login.respuestaXML(login.generarCMS(login.guardarXML()));
end;
end;
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.
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:
Código Delphi
[-]unit string64;
interface
uses
Winapi.Windows,Winapi.Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const
B64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
function Base64Decode(const S: string): string;
function Base64Encode(const S: String): String;
implementation
function Base64Encode(const S: String): String;
var
InBuf: array[0..2] of Byte;
OutBuf: array[0..3] of Char;
iI, iJ: Integer;
begin
SetLength(Result, ((Length(S) + 2) div 3) * 4);
for iI := 1 to ((Length(S) + 2) div 3) do
begin
if Length(S) < (iI * 3) then
Move(S[(iI - 1) * 3 + 1], InBuf, Length(S) - (iI - 1) * 3)
else
Move(S[(iI - 1) * 3 + 1], InBuf, 3);
OutBuf[0] := B64Table[((InBuf[0] and $FC) shr 2) + 1];
OutBuf[1] := B64Table[(((InBuf[0] and $3) shl 4) or ((InBuf[1] and $F0) shr 4)) + 1];
OutBuf[2] := B64Table[(((InBuf[1] and $F) shl 2) or ((InBuf[2] and $C0) shr 6)) + 1];
OutBuf[3] := B64Table[(InBuf[2] and $3F) + 1];
Move(OutBuf, Result[(iI - 1) * 4 + 1], 4);
end;
if Length(S) mod 3 = 1 then
begin
Result[Length(Result) - 1] := '=';
Result[Length(Result)] := '=';
end
else if Length(S) mod 3 = 2 then
Result[Length(Result)] := '=';
end;
function Base64Decode(const S: string): string;
var
OutBuf: array[0..2] of Byte;
InBuf: array[0..3] of Byte;
iI, iJ: Integer;
begin
if Length(S) mod 4 <> 0 then
raise Exception.Create('Base64: Incorrect string format');
SetLength(Result, ((Length(S) div 4) - 1) * 3);
for iI := 1 to (Length(S) div 4) - 1 do
begin
Move(S[(iI - 1) * 4 + 1], InBuf, 4);
for iJ := 0 to 3 do
case InBuf[iJ] of
43: InBuf[iJ] := 62;
48..57: Inc(InBuf[iJ], 4);
65..90: Dec(InBuf[iJ], 65);
97..122: Dec(InBuf[iJ], 71);
else
InBuf[iJ] := 63;
end;
OutBuf[0] := (InBuf[0] shl 2) or ((InBuf[1] shr 4) and $3);
OutBuf[1] := (InBuf[1] shl 4) or ((InBuf[2] shr 2) and $F);
OutBuf[2] := (InBuf[2] shl 6) or (InBuf[3] and $3F);
Move(OutBuf, Result[(iI - 1) * 3 + 1], 3);
end;
if Length(S) <> 0 then
begin
Move(S[Length(S) - 3], InBuf, 4);
if InBuf[2] = 61 then
begin
for iJ := 0 to 1 do
case InBuf[iJ] of
43: InBuf[iJ] := 62;
48..57: Inc(InBuf[iJ], 4);
65..90: Dec(InBuf[iJ], 65);
97..122: Dec(InBuf[iJ], 71);
else
InBuf[iJ] := 63;
end;
OutBuf[0] := (InBuf[0] shl 2) or ((InBuf[1] shr 4) and $3);
Result := Result + Char(OutBuf[0]);
end
else if InBuf[3] = 61 then
begin
for iJ := 0 to 2 do
case InBuf[iJ] of
43: InBuf[iJ] := 62;
48..57: Inc(InBuf[iJ], 4);
65..90: Dec(InBuf[iJ], 65);
97..122: Dec(InBuf[iJ], 71);
else
InBuf[iJ] := 63;
end;
OutBuf[0] := (InBuf[0] shl 2) or ((InBuf[1] shr 4) and $3);
OutBuf[1] := (InBuf[1] shl 4) or ((InBuf[2] shr 2) and $F);
Result := Result + Char(OutBuf[0]) + Char(OutBuf[1]);
end
else
begin
for iJ := 0 to 3 do
case InBuf[iJ] of
43: InBuf[iJ] := 62;
48..57: Inc(InBuf[iJ], 4);
65..90: Dec(InBuf[iJ], 65);
97..122: Dec(InBuf[iJ], 71);
else
InBuf[iJ] := 63;
end;
OutBuf[0] := (InBuf[0] shl 2) or ((InBuf[1] shr 4) and $3);
OutBuf[1] := (InBuf[1] shl 4) or ((InBuf[2] shr 2) and $F);
OutBuf[2] := (InBuf[2] shl 6) or (InBuf[3] and $3F);
Result := Result + Char(OutBuf[0]) + Char(OutBuf[1]) + Char(OutBuf[2]);
end;
end;
end;
end.
Entonces quise convertir el certificado con openssl directamente a base64. Para lo mismo hago lo siguiente:
c:\OpenSSL-Win32\bin\openssl.exe x509 -inform der -in G:\despensa\Win32\Debug\certificado\certificado.crt -out G:\despensa\Win32\Debug\certificado\certificado.pem
Pero me devuelve:
unable to load certificate 7200:error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag:.\crypto\asn1\tasn_dec.c:1320: 7200:error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error:.\crypto\asn1\tasn_dec.c:382:Type=X509