Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Internet (https://www.clubdelphi.com/foros/forumdisplay.php?f=3)
-   -   Facturas electrónicas [España] (https://www.clubdelphi.com/foros/showthread.php?t=89295)

espinete 28-10-2015 11:21:31

Facturas electrónicas [España]
 
NOTA DEL MODERADOR:=======================================
Como hemos hecho con otros temas similares, voy a "fijar" este hilo para dedcarlo a la Facturación electrónica (Facturae).
Además adjunto links de otros hilos (que no quedan fijados) sobre el mismo tema:

=======================================================

[Movido desde esta otra conversación]

Bueno, voy a aportar lo que tengo por ahora, que es la parte que crea el .XML (o .XSIG para la Admón. Pública) y lo firma con certificado digital o DNIe. Pero esto no puede quedarse ahí, hay que seguir con la tercera parte (que es la finalidad de este post), que es enviar la factura al webservice de face, a ver si entre todos lo conseguimos.

Con este código se puede crear un .XML (manualmente) y firmarlo para que sea válido y aceptado en la web de facturae. La web de facturae tiene un "validador online" aquí...
http://sedeaplicaciones2.minetur.gob.es/FacturaE/
...que nos servirá para comprobar si las facturas creadas son correctas, están bien firmadas, etc.

Componentes necesarios de SecureBlackBox: TEIWinCertStorage y TEIX509Certificate.
Otros componentes: TXMLDocument

(los componentes SecureBlackBox vienen con un montón de proyectos de ejemplo, entre ellos uno para firmar archivos PDF o XML).

Primera parte: crear el .XML

Yo he decidido crearlo "a mano". Un XML no es más que un archivo de texto con una estructura concreta, así que utilizo un Memo y le voy añadiendo las líneas con cuidado...
No voy a ponerlo todo porque es muy largo. Quizás más tarde subiré el código completo. El XML requiere un montón de valores variables que debemos rellenar (datos del emisor, receptor, factura, artículos, totales, impuestos...). Esta parte es algo tediosa, pero muy sencilla.

Antes de nada, cargamos los certificados digitales instalados en el sistema, por ejemplo en el OnCreate del Form1, en un ComboBox.
Esto hará que en el combobox veamos los certificados digitales instalados en Windows, ya sean certificados de empresa, el del DNIe, etc.
Nos harán falta más tarde para la firma del XML.

Código Delphi [-]
procedure TForm1.FormCreate(Sender: TObject);
var i:integer;
begin
    for i := 0 to WinCertStorage.Count - 1 do
    begin
      Cert := WinCertStorage.Certificates[i];
      ComboCertificate.Items.Add('Título: ' + Cert.SubjectName.CommonName + ', Emisor: ' + Cert.IssuerName.CommonName);
    end;
end;

Empezamos a crear el XML a mano...

Código Delphi [-]
      memo1.Lines.Clear;
      memo1.lines.append('');
      memo1.lines.append(' xmlns:fe="http://www.facturae.es/Facturae/2009/v3.2/Facturae" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"');
      memo1.lines.append('');
      memo1.lines.append('3.2');
      {.....}

Cuando tengamos todo el texto del XML, guardamos el memo en un archivo con extensión xml.

Código Delphi [-]
    memo1.Lines.SaveToFile('c:\pruebas\factura.xml');

Ahora abrimos el archivo con el componente XMLDocument para tenerlo ahí cargado, y lo volvemos a guardar. ¿por qué? Porque así se guarda bien estructurado, identado, etc. y es más fácil leerlo si nos hiciera falta.

Código Delphi [-]
    XMLDocument1.LoadFromFile(archivo);
    xmldocument1.Active:=true;
    xmldocument1.SaveToFile(archivo);

Y ahora a firmar...

Segunda parte: Firmar el XML

Este código no es mío. Tiene partes extraídas de clubdelphi, de los ejemplos de secureblackbox y de otros ejemplos de firma digital.
En el código le decimos qué certificado utilizar para la firma, de entre los cargados en el combobox anterior.
Probablemente el código se pueda mejorar. He dejado los comentarios del creador original porque explican bastante bien lo que se hace en él.

Código Delphi [-]
function Firma:boolean;
var XML_Doc:ElXMLDOMDocument;
    XML_Refs:TElXMLReferenceList;
    XML_RefDocu,XML_RefCert:TElXMLReference;
    XML_Signer:TElXMLSigner;
    XML_XAdES:TElXAdESSigner;
    XML_KeyData:TElXMLKeyInfoX509Data;
    XML_Nodo:ElXMLDOMNode;
    MS:TStream;
    XML_Buf:ByteArray;

    F: {$ifndef DELPHI_NET}TFileStream{$else}FileStream{$endif};
begin
    result:=false;
    with form1 do
    begin

          // ************************
          // ** Firmar fichero XML **
          // ************************

          XML_Doc:=     ElXMLDOMDocument.create;         //Documento XML a ser firmado
          XML_Refs:=    TElXMLReferenceList.create;          //Lista de nodos a ser firmados
          XML_RefDocu:= TElXMLReference.create;            //Nodo que representa al XML completo
          XML_RefCert:= TElXMLReference.create;             //Nodo del certificado utilizado
          XML_Signer:=  TElXMLSigner.Create(nil);            //Objeto firmante en XML...
          XML_XAdES:=   TElXAdESSigner.Create(nil);       //Sujeto firmante con info adicional XAdES
          XML_KeyData:= TElXMLKeyInfoX509Data.create(false); //Certificado a utilizar...

          try
            //Leo el fichero XML
            //==================
            // Por defecto en UTF-8 y normalizando los finales de linea (si llegan
            // CR+LF entonces la firma no se validará luego pues los digest no
            // deberían incluir estos CR+LF).

            {$ifndef DELPHI_NET}
            F := TFileStream.Create(archivo, fmOpenRead or fmShareDenyWrite);
            {$else}
            F := FileStream.Create(archivo, FileMode.Open, FileAccess.Read);
            {$endif}
            try
              XML_Doc.LoadFromStream(F, '', true);
            except
              on E : Exception do
              begin
                MessageDlg('Error: ' + E.Message, mtError, [mbOk], 0);
              end;
            end;

            FreeAndNil(F);

            if not XML_Doc.Loaded then
              raise Exception.create('Firma XML: No se pudo cargar el documento XML.');

            //Configuro el objeto firmador de XML
            //===================================
            // Se han de firmar la lista de nodos referenciados en XML_Refs
            XML_Signer.References:= XML_Refs;
            XML_Signer.SignatureMethodType:= xmtSig;
            // Se firma en formato "enveloped"
            XML_Signer.SignatureType:= xstEnveloped;
            // Se canoniza -ver ejemplos de la web facturae- el XML
            XML_Signer.CanonicalizationMethod:= xcmCanon;
            // La firma con el metodo mas estandar posible
            XML_Signer.SignatureMethod:= xsmRSA_SHA1;
            // La parte publica del certificado se debe incluir
            XML_Signer.IncludeKey:= true;
            // Necesito incrustar info adicional "XAdES" (XML Advanced Electronic Signatures )...
            XML_XAdES:= TElXAdESSigner.Create(nil);
            XML_Signer.XAdESProcessor:= XML_XAdES;

            //Configuro la parte XAdES del objeto firmador
            //============================================
            //
            // Se pide usar XAdES V1.2.2 o superior pero ha de ser "compatible" (?)
            // Algunos ejemplos usando V1.3.2 validan, asi que lo uso.
            XML_Signer.XAdESProcessor.XAdESVersion:= XAdES_v1_3_2;
            //SignerRole (en calidad de qué firmamos): supplier, customer o third_party
            //  Firmamos facturas emitidas por nosotros (supplier) en este caso.
            //  Si recibimos una factura firmada por el supplier, la podemos volver
            //  a firmar como que la damos por recibida usando "customer".
            //NOTA: El validador de www.facturae.es no comprueba este dato (2-2010)
            XML_Signer.XAdESProcessor.Included := [xipSignerRole];
            XML_Signer.XAdESProcessor.SignerRole.ClaimedRoles.AddText(
              XML_Signer.XAdESProcessor.XAdESVersion, XML_Doc, 'supplier');
            // La politica de firmado es la definida en el PDF V3.1 sobre firmado Facturae
            XML_Signer.XAdESProcessor.PolicyId.SigPolicyId.Identifier:= 'http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf';
            //NOTA: Si vas a la web, este PDF esta en otro link diferente (WTF):
            //'http://www.facturae.es/es-ES/Documentacion/Politicas/Politicas/Versi%C3%B3n%203_1/Politica_Firma_formato_facturae_v3_1.pdf';
            XML_Signer.XAdESProcessor.PolicyId.SigPolicyId.Description:= 'Política de firma electrónica para facturación electrónica con formato Facturae';
            //Ahora el hash del propio PDF en SHA1 (en la web esta debajo del PDF)...
            XML_Signer.XAdESProcessor.PolicyId.SigPolicyHash.DigestMethod:= 'http://www.w3.org/2000/09/xmldsig#sha1';
            //El digest en SHA-1 de la web (en formato hex), esta MAL, no corresponde
            //con ese PDF, y ademas, en su ejemplo de la V3.0 el digest es correcto
            //y NO se corresponde con el digest que dan!
            // '613c46e7bac7df5b266e6be0349b5fe8bb4944e2' ESTA MAL EN LA WEB
            //Uso el digest SHA1 correcto generado a partir del PDF:
            SetLength(XML_Buf,0); //Evito un warning tonto
            if not HexStr2ByteArray('3A18B197ABA90FA6AFF0DEE912F0C006110BEA13', XML_Buf) then
              raise Exception.Create('Firma XML: Error convirtiendo digest de política de firmado a Base64.');
            {NOTA: Sustituido por  HexStr2ByteArray()
            XML_Digest:= LowerCase('3A18B197ABA90FA6AFF0DEE912F0C006110BEA13');
            SetLength(XML_Buf, Length(XML_Digest) div 2);
            //En delphi 7 HexToBin va bien, en Delphis mas moderno, no vale porque
            //WideChar y pChar se tratan de forma diferente, asi que se hace a mano.
              //HexToBin(PChar(XML_Digest), PChar(XML_Buf), Length(XML_Digest) div 2);
            for j:= 0 to Length(XML_Buf)-1 do begin
              k1:= SBMath.HexToDecDigit(XML_Digest[j*2 + 1]);
              k2:= SBMath.HexToDecDigit(XML_Digest[j*2 + 2]);
              if (k1<0) or (k2<0) then
                raise Exception.Create('Firma XML: Error convirtiendo digest de política de firmado a Base64.');
              XML_Buf[j]:= k1 shl 4 + k2;
            end;}

            XML_Signer.XAdESProcessor.PolicyId.SigPolicyHash.DigestValue:= XML_Buf;

            //-----------------------
            // FECHA/HORA DE LA FIRMA
            //-----------------------
            //Anoto la fecha y hora del firmado (del PC no es legalmente valida)

            XML_Signer.XAdESProcessor.SigningTime := LocalTimeToUTCTime(Now);

            //Pre-Genero los nodos de firmado necesarios
            //==========================================
            // Esta es la parte mas "confusa": He de firmar partes del XML que no
            // existen hasta que este firmado ¿Como? El objeto Signer, junto con el
            // prepocesador XAdES, crean esos nuevos nodos para que podamos
            // referenciarlos -decirle al firmador que son unas referecnias a ser
            // firmadas- y al final, al firmar, unira el XML original con estos
            // nuevos nodos -formato de firma "enveloped"- y se graba el XML final.
            //
            // Primero genero la zona de informacion XAdES:
            XML_Signer.XAdESProcessor.Generate;
            // Ahora, al objeto firmador "generico" le pido lo mismo, con lo que
            // tambien se crea el nodo "KeyInfo" con el certificado a utilizar.
            XML_Signer.UpdateReferencesDigest;
            //Cargo certificado para firmar
            //=============================
            // Cargo el certificado a usarse en la firma en el objeto firmador
            // NOTA: Se incluira la parte publica del certificado en base64.
            // De los certificados que me pases, elijo el primero con parte privada,
            // ya que no quiero incluir el certificado raiz del emisor (no se
            // menciona en la politica de firmado y no aparece en los ejemplos):

            XML_KeyData.IncludeKeyValue:= true;
           Cert := WinCertStorage.Certificates[comboCertificate.ItemIndex];
           if Cert.PrivateKeyExists then
               XML_KeyData.certificate:= Cert;

            //Compruebo que ha quedado algún certificado válido...
            if not Assigned(XML_KeyData.certificate) then
              raise Exception.create('FirmaXML: No se cargó un certificado válido para firmar, no contiene clave privada.');
            XML_Signer.KeyData:= XML_KeyData;
            //Añado a la lista de nodos a firmar
            //==================================
            //NOTA: El metodo SHA1 no es el mas seguro, pero el SHA256 no se usa mucho
            //y puede dar problemas (WinXP SP2 no lo admite) por eso el DNIe tampoco
            //lo usa aun, asi que mejor dejo el valor por defecto.
            //
            // NODO 1: Se ha de firmar el documento original XML completo...
            //
            XML_RefDocu.DigestMethod:= xdmSHA1; //El mas estandard/compatible de todos
            XML_RefDocu.URINode:= XML_Doc.DocumentElement; //El XML completo
            XML_RefDocu.URI:= ''; //No hay nombre especifico para este nodo
            // En los ejemplos aparece la transformacion "Enveloped-Signature"
            // aunque en el PDF de la firma V3.1 no lo menciona.
            XML_RefDocu.TransformChain.Add(TElXMLEnvelopedSignatureTransform.Create);
            XML_Refs.Add(XML_RefDocu); //Lo sumo a las cosas a ser firmadas
            // Actualizo digest con este nuevo nodo.
            XML_Signer.UpdateReferencesDigest;
            // NODO 2: Se han de firmar las propiedades de firmado (XAdES)....
            //
            // El nodo SignedProperties es parte de la informacion XAdES que se
            // creo en XML_Signer.XAdESProcessor.Generate y se firma siempre en
            // el standard XAdES. Dejo el codigo abajo como referencia solo, pero
            // no funcionaría si se activa porque ya esta siendo firmado una vez.
            //XML_Signer.XAdESProcessor.QualifyingProperties.SignedProperties.ID:= 'SignedPropertiesID';
            //XML_RefProp.DigestMethod:= xdmSHA1;
            //XML_RefProp.URI:= '#SignedPropertiesID';
            //XML_Refs.Add(XML_RefProp);
            //XML_Signer.UpdateReferencesDigest;
            //
            // NODO 3: Se ha de firmar el propio certificado utilizado
            //
            // El nodo KeyInfo ha de ir identificado por 'Certificate1' -segun los
            // ejemplos, el PDF no da ningun nombre concreto-
            // si se firma dos veces un mismo XML no chocan aunque el
            // nombre coincida por estar dentro de diferentes nodos "Signature".

            XML_RefCert.URI:= '#Certificate1';
            XML_RefCert.DigestMethod:= xdmSHA1;
            XML_Refs.Add(XML_RefCert); //Lo sumo a las cosas a ser firmadas
            //Como ya tengo todos los nodos, ahora genero la firma y consigo que
            //exista el nodo KeyInfo, de forma que pueda darle el nombre que le toca
            //antes de unir el XML original con el nuevo nodo de la firma.
            XML_Signer.Sign;
            XML_Signer.Signature.KeyInfo.ID:= 'Certificate1';
            //Añado la firma al XML
            //=====================
            XML_Nodo:= ElXMLDOMNode(XML_Doc.DocumentElement);
            XML_Signer.Save(XML_Nodo);
            //Actualizo el stream MS con el XML final firmado.

            {$ifndef DELPHI_NET}
            F := TFileStream.Create(archivo, fmCreate or fmOpenWrite);
            {$else}
            F := System.IO.FileStream.Create(archivo, FileMode.Create, FileAccess.ReadWrite);
            {$endif}
            try
              XML_Doc.SaveToStream(F, xcmNone, '');
            except
              on E : Exception do
              begin
                MessageDlg('Error: ' + E.Message, mtError, [mbOk], 0);
              end;
            end;
            FreeAndNil(F);

            result:= true;
          finally
            //NOTA: Los XML_Ref se destruyen solos junto al XML_Refs
            XML_Doc.free;
            XML_Refs.free;
            XML_Signer.free;
            XML_XAdES.free;
            XML_KeyData.Free;
          end;
    end;
end;

Y ya tenemos firmado nuestro XML. Y si el certificado es válido, la fecha/hora es válida, etc. entonces la factura electrónica es perfectamente legal. Podremos comprobarlo en la web de validación de facturae, que además nos dirá donde está el error, si lo hubiera.

IMPORTANTE:
Cuando la factura es para una Administración Pública, el .XML debe tener unas secciones obligatorias (Oficina contable, órgano gestor, etc.) y el archivo, en vez de tener extensión XML, lo guardaremos al final como .xsig. Y eso es todo.

Y ahora, lo difícil...

Enviar la factura al webservice...

Importamos el WSDL del webservice del face desde Delphi. Hay dos versiones, la de producción y la de desarrollo para pruebas. El de pruebas es este: https://se-face-webservice.redsara.es/sspp?wsdl

En el IDE, vamos a Component -> Import WSDL e indicamos la url anterior. Siguiente -> Siguiente y Finish.
Esto creará una unit llamada 'sspp.pas', que guardaremos en la misma carpeta de nuestro proyecto, y añadiremos al uses de nuestro form.

Y hasta aquí puedo leer. Y no porque no quiera seguir, sino porque no tengo ni idea.

Este es el código, supuestamente, para enviar la factura al webservice, pero antes hay que hacerle varias cosas que desconozco. Creo que incluso puede hacerse sin los SecureBlackBox, pero de verdad que estoy atascado.

Código Delphi [-]
var facturasspp : SSPPFactura;
    fichero_fac : SSPPFicheroFactura;
    answ : SSPPResultadoEnviarFactura;
    PO:SSPPWebServiceProxyPort;
begin
          PO := GetSSPPWebServiceProxyPort(FALSE, '', nil);
          facturasspp := ssppfactura.Create;
          facturasspp.correo := 'correo@micorreo.com';
          fichero_fac := ssppficherofactura.Create;
          fichero_fac.nombre := 'c:\pruebas\factura.xml';
          fichero_fac.factura := '12345';
          fichero_fac.mime := 'application/xml';
          facturasspp.fichero_factura := fichero_fac;

          try
            answ:=PO.enviarFactura(facturasspp);
          except
            on e:exception do showmessage(e.Message);
          end;
end;

newtron 28-10-2015 11:30:19

Cita:

Empezado por espinete (Mensaje 498479)
Bueno, voy a aportar lo que tengo por ahora

Espinete, gracias por aportar tu código pero no es eso lo que te quería decir. Yo en particular no tengo las SBB y no podría usar ese código para firmar el fichero, y la verdad es que no me apetece comprar los componentes solo para eso. Lo que te decía es si puedes preparar un .exe que dándole el fichero de entrada y el de salida te firme el fichero de entrada y genere el de salida firmado para poder usarlo como programa externo.

Luego ya vendrá el tema del webservice (que yo en particular tampoco tengo ni idea).

Saludos

espinete 28-10-2015 14:47:54

Si le vas a dar uso a los componentes, porque los necesitas, no te quedará más remedio que comprarlos. De nada te sirve que te haga yo un ejecutable "puente", sin su código fuente, y que dentro de un mes pueda fallar, o requerir más parámetros, o requerir una nueva versión del SecureBlackBox, quedándote obsoleto. Lo de "no me apetece comprar los componentes solo para eso"... no sé, depende de a qué te refieras exactamente con "solo para eso". Yo creo que es bastante.

Tengo entendido que hay otras formas de firmar el XML sin usar los SecureBlackBox, pero lo desconozco. Este post es para intentar averiguar entre todos cómo enviar las facturas al webservice.

Neftali [Germán.Estévez] 29-10-2015 13:07:15

Cita:

Empezado por espinete (Mensaje 498479)
Este es el código, supuestamente, para enviar la factura al webservice, pero antes hay que hacerle varias cosas que desconozco. Creo que incluso puede hacerse sin los SecureBlackBox, pero de verdad que estoy atascado.

Pues yo creo que ya tienes lo más complicado.
Para el tema del envío no hacen falta los BlackBox.

Un código similar al que pones debería funcionar:

Código Delphi [-]
var
  resultado:SSPPResultadoEnviarFactura;
  facturaWS:SSPPFactura;
begin

  facturaWS := SSPPFactura.Create();
  facturaWS.correo := ...
  ...

  // Según el rellenado llamamos a uno u otro
  resultado := (HTTPRIO1 as SSPPWebServiceProxyPort).enviarFactura(facturaWS);

La configuración del componente supongo que es la que toca:



A partir de ese punto, ¿obtienes algún error? ¿Alguna respuesta?

espinete 29-10-2015 18:22:10

Hola, Netfali

El error que obtengo es el mismo que utilizando la otra forma de hacer el envío:

La petición SOAP no está bien construida: No se encuentra el SOAP Header

El componente HTTPRIO está bien configurado (igual que en tu imagen).

Así ha quedado el código por ahora:

Código Delphi [-]
var facturasspp : SSPPFactura;
    fichero_fac : SSPPFicheroFactura;
    answ : SSPPResultadoEnviarFactura;
    PO:SSPPWebServiceProxyPort;
begin
    if opendialog1.Execute then   //Para elegir el .XML que queremos enviar al webservice
    begin
          PO := GetSSPPWebServiceProxyPort(FALSE, '', nil);
          facturasspp := ssppfactura.Create;
          facturasspp.correo := 'XXXXXXXXXX';

          fichero_fac := ssppficherofactura.Create;
          fichero_fac.nombre := extractfilename(opendialog1.FileName);
          fichero_fac.factura := '72345';
          fichero_fac.mime := 'application/xml';

          facturasspp.fichero_factura := fichero_fac;

          try
              answ := (HTTPRIO1 as SSPPWebServiceProxyPort).enviarFactura(facturasspp);
          except
              on e:exception do showmessage(e.Message);
          end;
    end;

end;

Neftali [Germán.Estévez] 30-10-2015 09:33:34

No se si lo has puesto, no me ha parecido leerlo.
¿Con qué versión de delphi estás trabajando?

espinete 30-10-2015 10:18:56

Hola...

Con Delphi XE7 o con Delphi Seattle.

Un saludo

Neftali [Germán.Estévez] 30-10-2015 11:00:38

Vale.
Es porque con los antiguos D6/D7 había un problema con las cabeceras de las Indy.
En algunos casos había que añadir algo manualmente.

Neftali [Germán.Estévez] 30-10-2015 11:03:43

¿Puedes hacer algún envío envío de pruebas desde algún otro sitio? ¿Aplicación de ejemplo? ¿web?

Lo digo para intentar hacer un envío de ejemplo que funcione, y utilizando algún debugger (tipo Fiddler) intertar ver la cabecera completa que se está enviando.
Una vez que tengas la cabecera completa que se envía cuando el envío es correcto, intentar ver qué estás enviando cuanto utilizas la Indy y ver si hay diferencias.

No se si me explico...

espinete 30-10-2015 11:33:15

Hola

He hecho la prueba con el Fiddler 4 "escuchando". Tengo un montón de información, pero no sé cómo interpretarla o donde debo mirar.

Request Header:
Código:

POST /sspp HTTP/1.1
SOAPAction: "https://webservice.face.gob.es#enviarFactura"
Content-Type: text/xml; charset=utf-8
User-Agent: CodeGear SOAP 1.3
Host: se-face-webservice.redsara.es
Content-Length: 960
Connection: Keep-Alive
Cache-Control: no-cache

Request TextView:
Código:

<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body xmlns:NS1="https://webservice.face.gob.es" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><NS1:enviarFactura><facturaWS href="#1"/></NS1:enviarFactura><NS1:SSPPFactura id="1" xsi:type="NS1:SSPPFactura"><correo xsi:type="xsd:string">prueba@miemail.com</correo><fichero_factura href="#2"/><ficheros_anexos xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="NS1:SSPPFicheroAnexo[0]"/></NS1:SSPPFactura><NS1:fichero_factura id="2" xsi:type="NS1:SSPPFicheroFactura"><factura xsi:type="xsd:string">72345</factura><nombre xsi:type="xsd:string">Factura 177.xml</nombre><mime xsi:type="xsd:string">application/xml</mime></NS1:fichero_factura></SOAP-ENV:Body></SOAP-ENV:Envelope>


Neftali [Germán.Estévez] 30-10-2015 11:37:59

Dentro de "Inspectors" tienes los "Headers", la sección "Auth" y "Raw", que por ahora parece que es lo que nos debe interesar.

El siguiente paso sería comparar eso, con un envío correcto.

espinete 30-10-2015 12:44:29

2 Archivos Adjunto(s)
Hola

En Fiddler tengo 4 procesos. Supongo que 2 envíos y dos respuestas. No sé muy bien cómo funciona fiddler y si debo mirar en la parte superior derecha o inferior derecha de cada evento:
Archivo Adjunto 3277

En los envíos, la pestaña Auth dice lo siguiente:
Código:

No Proxy-Authorization Header is present.

No Authorization Header is present.

De todas formas, es demasiada información para poner aquí. La he exportado y subido aquí Archivo Adjunto 3278

Ñuño Martínez 02-11-2015 12:12:40

Veo un problema en esto: ¿Es imperativo usar SOAP? Lo digo por los que no usamos (o usamos poco) el Windows. La verdad es que no sé si ese protocolo está disponible para otros sistemas operativos (no sólo Linux, también UNIX, MacOS, Solaris, BSD, OS/400, eComStation...), pero teniendo en cuenta la deriva de la administración española en lo referente a nuevas tecnologías que en realidad no son tan nuevas, no sería la primera vez que hacen algo que únicamente puede usarse desde Windows (¡Hola, DNI electrónico!).

espinete 02-11-2015 12:27:33

Pues no lo sé, pero ya bastante tengo con que no lo consiga hacer desde Windows. Supongo que habilitarán más protocolos en el futuro.

Casimiro Notevi 02-11-2015 12:53:26

Cita:

Empezado por espinete (Mensaje 498733)
.... Supongo que habilitarán más protocolos en el futuro.

:rolleyes:

iMia 02-11-2015 17:04:21

SOAP (Simple Object Access Protocol) no es exclusivo de windows... es un protocolo de comunicaciones, para el intercambio de información... Inicialmente en xml...

Espinete... seguramente el problema lo tienes que al importar el WSDL, hay que hacer un cambio por que el server Tomcat, requiere SOAP 1.1 y no 1.2...
¿donde? os preguntareis...

Al registrar las opciones de invocación, hay que cambiar el TypeInfo, del tipo ioDocument por ioDefault
con eso queda arreglado...

Código:

//  InvRegistry.RegisterInvokeOptions(TypeInfo(xxxx), ioDocument);
  InvRegistry.RegisterInvokeOptions(TypeInfo(xxxx), ioDefault);


Saludos

espinete 03-11-2015 11:05:56

Hola, iMia...

Gracias por tu aportación... pero tras importar el WSDL, en la unit resultante (sspp.pas), no hay ninguna referencia a InvRegistry.RegisterInvokeOptions(), por lo que no puedo sustituirlo (Delphi XE7 y Delphi Seattle).

Lo más parecido está en la parte initialization de esa unit:

Código Delphi [-]
initialization
  { SSPPWebServiceProxyPort }
  InvRegistry.RegisterInterface(TypeInfo(SSPPWebServiceProxyPort), 'https://webservice.face.gob.es', 'UTF-8');
  InvRegistry.RegisterDefaultSOAPAction(TypeInfo(SSPPWebServiceProxyPort), 'https://webservice.face.gob.es#%operationName%');
  {...}
  {...}

¿Dónde exactamente debo hacer el cambio que sugieres?

iMia 03-11-2015 11:19:26

Si correcto, lo acabo de importar para verlo...
y no aparece esa linea....
prueba de añadirla tal que...

Código:

InvRegistry.RegisterInvokeOptions(TypeInfo(SSPPWebServiceProxyPort), ioDefault);
justo debajo de las dos que muestras...

Saludos...

Yo tambien en XE7...

espinete 03-11-2015 11:31:43

1 Archivos Adjunto(s)
Hecho.

Pero sigue igual:

La petición SOAP no está bien construida: no se encuentra el SOAP Header.

iMia 03-11-2015 11:46:00

Uffff...

Voy a probar..
pero al importar el wsdl, el codigo me da un error...
En la funcion "consultarListadoFacturas" el parámetro pone que es de tipo Array... pero no de que es ese array...

Código:

function consultarListadoFacturas(const listadoFacturas: Array): ArrayOfSSPPResultadoConsultarFactura; stdcall;
¿como lo has arreglado tu?

espinete 03-11-2015 12:24:12

Para poder compilar y empezar a hacer pruebas, lo "solucioné" con un "array of string".

No sé si será la solución, pero al menos podremos empezar a probar cosas y poco a poco ir avanzando.

Ñuño Martínez 03-11-2015 14:28:11

Cita:

Empezado por iMia (Mensaje 498745)
SOAP (Simple Object Access Protocol) no es exclusivo de windows... es un protocolo de comunicaciones, para el intercambio de información... Inicialmente en xml...

Gracias por la info. ^\||/

iMia 04-11-2015 08:39:26

bueno os informo de los progresos que estamos haciendo con espinete...

Empezado por espinete
Cita:

Creo que tengo algo...

He buscado por cómo añadir headers a la petición soap antes de hacer el envío y encontré un post en StackOverflow (en Delphi):
http://stackoverflow.com/questions/2...814937#2814937

Concretamente, en el evento BeforeExecute del componente HTTPRIO, obtenemos la petición que se va a enviar y la podemos "cambiar" sobre la marcha. Precisamente este usuario necesitaba hacer algo parecido a lo que intentamos nosotros (añadir la sección headers).

Luego he usado ese mismo evento para obtener lo que envía nuestra aplicación al webservice, en un Memo, y efectivamente, falta la sección <soapenv:Header> en el XML.

Bien

No obstante, no creo que esta sea la forma de conseguirlo. El XML podemos rellenarlo antes (incrustar la sección header con la firma, certificado, etc.). Lo que no me parece muy lógico es tener que firmar 2 veces el XML (una para firmar la factura y otra para el header), pero bueno, supongo que es normal.
Sí, aunque lo normal sería algo así...


Código Delphi [-]
var
  sphdr: TSOAPHEADERS;
  htpr: THTTPRIO;
  ws: SSPPWebServiceProxyPort;
...
begin
  sphdr := TSOAPHEADERS.create;
  htpr := THTTPRIO.create(self);
  htpr.SOAPHeaders.Send(sphdr);
  ws := GetSSPPWebServiceProxyPort(false, '', htpr);
...

Pero hay que enviar en la cabecera, todo el mensaje entero firmado...
por lo que hay que parsearlo o meterlo a mano antes de enviarlo con el evento BeforeExecute del httprio.
Es decir, en el evento, se coge el mensaje que se va a enviar (lo que hay en body, aunque contenga documentos firmados o no...), se firma, y se mente en la cabecera.
De esta forma ellos pueden comprobar que el mensaje que les llega, no está manipulado, ya que les llega sin firmar y firmado, comprueba la firma y si es correcta, al abrirlo, lo comparan con lo enviado... si es igual, está todo correcto y continúan...

Por lo que para ello hay que definir la función que capturará en evento...

Código Delphi [-]
 
  procedure HTTPRIO_BeforeExecute(const MethodName: string; SOAPRequest: TStream);

definir el httprio, asignarle el evetno y utilizarlo como vehículo del webservice...

Código Delphi [-]
var
  ws: SSPPWebServiceProxyPort;
  ...
begin
  htpr := THTTPRIO.create(self);
  htpr.OnBeforeExecute := HTTPRIO_BeforeExecute;

  ws := GetSSPPWebServiceProxyPort(false, '', htpr);
...

el evento se define tal que...

Código Delphi [-]
procedure TMDIMainForm.HTTPRIO_BeforeExecute(const MethodName: string; SOAPRequest: TStream);
var
  xmlCall: TXMLDocument;
begin
   try
      // Posicionarse al inicio del stream
      SOAPRequest.Position := 0;
      // pasar el stream a un xmlDoc... o donde se quiera...
      xmlCall.LoadFromStream(SOAPRequest);
      // Firmarlo y meterlo en la cabecera...

...

espinete 04-11-2015 11:48:25

Hola

Bien, vamos avanzando...

He conseguido añadir una cabecera a la petición, y gracias al evento BeforeExecute, puedo ver que efectivamente ahora el XML sí tiene sección cabecera:

Código:

<SOAP-ENV:Header SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:NS1="urn:Soap.InvokeRegistry">
<NS1:TSOAPHeader xsi:type="NS1:TSOAPHeader"/>

Casi lloro.

Bien. Yo lo he hecho de otra forma, no sé si ves algún problema:

Código Delphi [-]
procedure TForm1.Button2Click(Sender: TObject);
var facturasspp : SSPPFactura;
    fichero_fac : SSPPFicheroFactura;
    answ : SSPPResultadoEnviarFactura;
    PO:SSPPWebServiceProxyPort;

    htpr: THTTPRIO;
    sphdr: TSOAPHEADER;
    ws: SSPPWebServiceProxyPort;
begin
    if opendialog1.Execute then
    begin
        //Creamos los headers, necesarios para la petición SOAP
        sphdr := TSOAPHEADER.create;

        //Aquí rellenaríamos la sección header, pero no sé si es el mejor lugar.        

        PO := GetSSPPWebServiceProxyPort(FALSE, '', nil);
        facturasspp := ssppfactura.Create;
        facturasspp.correo := 'prueba@miemail.com';
        fichero_fac := ssppficherofactura.Create;
        fichero_fac.nombre := extractfilename(opendialog1.FileName);
        fichero_fac.factura := '72345';
        fichero_fac.mime := 'application/xml';
        facturasspp.fichero_factura := fichero_fac;

        //Aquí asignamos los headers que hemos creado a nuestra petición soap
        HTTPRIO1.SOAPHeaders.Send(sphdr);

        try
            answ := (HTTPRIO1 as SSPPWebServiceProxyPort).enviarFactura(facturasspp);
        except
            on e:exception do showmessage(e.Message);
        end;
    end;

end;

Tu código es diferente, creo que utilizas un XMLDocument que contendría las cabeceras ya rellenadas?

Ahora lo complicado es cómo meter el mensaje FIRMADO en la cabecera... ¿A mano?

Por ejemplo, en la petición SOAP que uso para pruebas, el body es este:

Código:

<SOAP-ENV:Body xmlns:NS2="https://webservice.face.gob.es" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<NS2:enviarFactura>
  <facturaWS href="#2"/>
</NS2:enviarFactura>
<NS2:SSPPFactura id="2" xsi:type="NS2:SSPPFactura">
  <correo xsi:type="xsd:string">prueba@miemail.com</correo>
  <fichero_factura href="#3"/>
  <ficheros_anexos xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="NS2:SSPPFicheroAnexo[0]"/>
</NS2:SSPPFactura>
<NS2:fichero_factura id="3" xsi:type="NS2:SSPPFicheroFactura">
  <factura xsi:type="xsd:string">72345</factura>
  <nombre xsi:type="xsd:string">Factura 177.xml</nombre>
  <mime xsi:type="xsd:string">application/xml</mime>
</NS2:fichero_factura>

Cómo se "firma" ese trozo de texto?

Aquí tengo una muestra de petición "REAL" sacada de la documentación, que debería servirnos como ejemplo, aunque a estas alturas ya dudo de lo que puede servir y lo que no.
Le he quitado la parte de los ficheros anexos (por ahora) para simplificarlo un poco.

Código:

<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="https://webservice.face.gob.es">

<soapenv:Header>

    <!-- // Security Content -->

</soapenv:Header>

<soapenv:Body>

<web:enviarFactura soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<facturaWS xsi:type="sspp:SSPPFactura">

    <!--You may enter the following 3 items in any order-->
      <correo xsi:type="xsd:string">XXXX correo electronicoXXXX</correo>
      <fichero_factura xsi:type="sspp:SSPPFicheroFactura">
   
      <!--You may enter the following 3 items in any order-->
      <factura xsi:type="xsd:string"> _contenido enbase_64 del fichero factura_ </factura>
      <nombre xsi:type="xsd:string"> _nombre del ficherofactura_ </nombre>
      <mime xsi:type="xsd:string"> _mimeType del ficherofactura_ </mime>
    </fichero_factura>

</facturaWS>
</web:enviarFactura>

</soapenv:Body>
</soapenv:Envelope>

Y el ejemplo de Security-Content que pone en la documentación es este (lo que va dentro del header):

Código:

<wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" wsu:Id="CertId-5A5C126069B253F2B0135998798458616" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
MIIEpDCCBA2gAwIBAgIEPLPTKTANBgkqhkiG9w0BAQUFADA2MQswCQYDVQQGEwJFUzENMAsGA1UEChMERk5NVDEYMBYGA1UECxMPRk5NVCBDbGFzZSAyIENBMB4XDTALS6PmAJWFoOUT3Xvp8UxYptb9/YK93ykPj5NYLcsXeh8L9SRWbFSnozoiATZoECDnrcMd054DdPrNVYLTZNhZ9Y2U9JqJpnIWR+a64Mo3iiMk/KBkI2jo3QIuaCjvPK+k6LQCwTIaRvnHGRxwIDAQABo4IB1DCCAdAwgdgGA1UdEQSB0DCBzaSByjCBxzEYMBYGCSsGAQQBrGYBDxMJUzI4MjYwMTVGMUMwQQYJKwYBBAGsZgEOEzRJTlRFUlZFVc9fS1I6qgUkmwCZKHiwgJ4tS1Mv3gKMZ+8ulc8JErYo661ql3GVmLsfdH5g3eWyC5rBEcCjkHSKO0qDhzg==

</wsse:BinarySecurityToken>

<ds:Signature Id="Signature-11" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethodAlgorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#id-12">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethodAlgorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>vfoQe7yobzrB5LzQZ/HD4B2F1BY=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
HOZFzxAsMAH8BDbuXOHekl+yyLXfodmPka5727t3LDFSkbxICkL92wy6dSbWyU07zK/dhfLl2a4c
33FcvOxAtYAEvQVRLcQM3VU9+L2SX9NReQaGTPPmtBb8UAWeH5m56nM9uxT7yIwfO424+lNEYEeo
1pYC+0DBI6WcN4LRgV4=

</ds:SignatureValue>
<ds:KeyInfo Id="KeyId-5A5C126069B253F2B0135998798458717">
<wsse:SecurityTokenReference wsu:Id="STRId-5A5C126069B253F2B0135998798458718"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:ReferenceURI="#CertId-5A5C126069B253F2B0135998798458616"
ValueType="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wsse:SecurityTokenReference>
</ds:KeyInfo>
</ds:Signature>
<wsu:Timestamp wsu:Id="Timestamp-10" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsu:Created>2013-02-04T14:26:24.586Z</wsu:Created>
<wsu:Expires>2013-02-04T14:31:24.586Z</wsu:Expires>
</wsu:Timestamp>
</wsse:Security>

Los componentes de SecureBlackBox permiten firmar peticiones SOAP usando certificados de diferentes tipos, y añaden la sección security al header, PERO no de la misma forma que en este ejemplo. Además, estaría bien saber si es posible hacerlo sin dichos componentes, ya que hay muchos usuarios que no los utilizan.

iMia 04-11-2015 12:42:14

Bien!!
bueno, varias cosas.... yo no utilizo SBB... Además, no estoy programando nada sobre el Face, aún... me pondré más adelante... simplemente te intentaba ayudar un poco...

En el tema de la cabecera, el código que pones yo lo haría diferente... simple cuestión de estilo...

Código Delphi [-]
procedure TForm1.Button2Click(Sender: TObject);
var 
    facturasspp : SSPPFactura;
    fichero_fac : SSPPFicheroFactura;
    answ : SSPPResultadoEnviarFactura;


    htpr: THTTPRIO;
    sphdr: TSOAPHEADER;
    WS: SSPPWebServiceProxyPort;
begin
    if opendialog1.Execute then
    begin
        //Creamos los headers, necesarios para la petición SOAP
        sphdr := TSOAPHEADER.create;

        // Creo el HTTP, que utilizaré como vehiculo, así no tengo que hacer Typecasts que no me gustan nada...
        htpr := THTTPRIO.create(Self);

        //Aquí trabajamos con el propio httprio, donde podemos poner cabeceras, asignar eventos, etc...

        htpr.SOAPHeaders.Send(sphdr);
        // htpr.OnBeforeExecute := BeforeExecuteHttpRio(...
        // htpr.OnAfterExecute := AfterExecuteHttpRio(...

        // Al crear la instancia del WS, le asigno el httprio que he creado y he manipulado...

        WS:= GetSSPPWebServiceProxyPort(FALSE, '', htpr);

        facturasspp := ssppfactura.Create;
        facturasspp.correo := 'prueba@miemail.com';
        fichero_fac := ssppficherofactura.Create;
        fichero_fac.nombre := extractfilename(opendialog1.FileName);
        fichero_fac.factura := '72345';
        fichero_fac.mime := 'application/xml';
        facturasspp.fichero_factura := fichero_fac;

        try
            // Me cargo el typecast... que para eso se ha instanciado el ws sobre la variable WS
            answ := WS.enviarFactura(facturasspp);
        except
            on e:exception do showmessage(e.Message);
        end;
    end;

end;

el tema de utilizar un TxmlDocuemnt, es simplemente para ponerlo de ejemplo, para firmarlo y re-inyectarlo en la cabecera.
Se puede hacer como quieras...

El tema de lo que debe ir en el header... ese ya es otro tema que habría que aclarar...

Si con SBB te permite firmar llamadas SOAP, esta claro que debe ir en el header... capturar la llamada, firmarla, inyectarla en el header y seguir enviando la llamada original, con la llamada firmada en el header... (donde pone "security content")...

espinete 04-11-2015 13:53:15

2 Archivos Adjunto(s)
Bueno...

Estoy viendo las demos de SecureBlackBox, que permiten añadir la firma al header del soap, pero tienen varias opciones y compatibilidad con varios certificados, así que habrá que averiguar cual hay que elegir.
Estas capturas de pantalla son de la aplicación demo que se incluye en los SecureBlackBox.
La aplicación nos permite abrir un XML y, entre otras cosas, añadir una firma, verificarla, quitarla, o actualizarla, y volver a guardar el XML resultante ya firmado.

Lo primero que nos pregunta es el Signature Handler. 3 opciones. Yo he elegido la WSS Signature Handler, que no sé si es la correcta porque NO ME ENTERO DE NADA:
Archivo Adjunto 3285

En esa misma ventana nos pregunta si queremos crear la cabecera, o sustituir una ya existente. Como nuestro XML no tiene cabecera, le decimos que la cree.
Además, hay otra opción "sign body" que no sé qué hace. Tendré que hacer pruebas diferentes y comparar un resultado con otro para entenderlo mejor.

El siguiente paso es elegir el certificado con el que queremos firmar el XML, el Key Name, etc. y aquí tenemos otras opciones que debemos elegir de una lista: Embed Certificate Option, que supongo que es la forma en la que se incrusta el certificado:
Archivo Adjunto 3286

Como tampoco tengo ni idea, pues tendré que hacer pruebas y rezar para que alguna de ellas funcione.

Una vez hemos añadido la firma y el securiyu header al XML, la sección HEADER de nuestro XML quedará así (más o menos)

Código:

<SOAP-ENV:Header SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:NS1="urn:Soap.InvokeRegistry">
<NS1:TSOAPHeader xsi:type="NS1:TSOAPHeader"/>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:BinarySecurityToken ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" wsu:Id="id-278627682" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">MIIIkTCCB3mgAwIBAgIJAK2....................................</wsse:BinarySecurityToken>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="#id-1455977767">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>pyHVuQqgYvQ3F1PIb+Cw39jfq2M=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo><ds:SignatureValue>kDLUYUxnRCmiR6TPwBAg/..................................</ds:SignatureValue><ds:KeyInfo><ds:KeyValue><ds:RSAKeyValue>
<ds:Modulus>AIHDAKJSAS.....................................................</ds:Modulus>
<ds:Exponent>AQAB</ds:Exponent>
</ds:RSAKeyValue></ds:KeyValue>
<wsse:SecurityTokenReference wsu:Id="" TokenType="" Usage="" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:Reference URI="#id-278627682" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
</wsse:SecurityTokenReference><ds:X509Data><ds:X509Certificate>MIIIkTCCB3mgAw...................</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</wsse:Security>
</SOAP-ENV:Header>

Esto se parece más al header del XML que aparece en la documentación del face, aunque no es exacto (hay muchas más cosas).

Durante estos días intentaré probar todas las opciones y averiguar si es posible crear el tipo de firma que exige face.

iMia 04-11-2015 15:25:21

Tal uy como te puse por privado, según el manual de los servicios Web....

Las peticiones tanto como las respuestas deben ir firmadas según el estandar OASIS WSSecurity 1.0 X509 Token Profile
http://en.wikipedia.org/wiki/WS-Security
http://docs.oasis-open.org/wss/2004/...rofile-1.0.pdf
La plataforma FACe delega sobre la plataforma @firma
(http://administracionelectronica.gob.es/ctt/afirma) la validación y la firma electrónica
digital de los servicios web, por lo que usted puede encontrar la documentación completa
en la misma.

Neftali [Germán.Estévez] 04-11-2015 15:25:28

Aunque no estoy interviniendo actívamente porque ahora no estoy con este tema, agradeceros (al menos a nivel personal) toda la información detallada que estáis adjuntando en este hilo.

Creo que antes o después nos será útil a michos de nosotros.

newtron 04-11-2015 16:51:49

Cita:

Empezado por Neftali (Mensaje 498857)
Aunque no estoy interviniendo actívamente porque ahora no estoy con este tema, agradeceros (al menos a nivel personal) toda la información detallada que estáis adjuntando en este hilo.

Creo que antes o después nos será útil a michos de nosotros.

Totalmente de acuerdo.

espinete 04-11-2015 17:24:00

Esta mañana he conseguido averiguar qué tipo de firma usar, y comparando los resultados con el de la dlcumentacion, creo que es la correcta.

Hasta mañana no podré seguir probando, pero en cuanto avance algo más lo iré publicando.

Eso sí, habrá que hacerlo con los SecureBlackBox. Al menos yo no sé si habrá otro método...

espinete 05-11-2015 11:17:32

1 Archivos Adjunto(s)
Bueno, creo que lo he conseguido :)

Aquí pongo una captura de pantalla de la respuesta que me da el webservice después de crear la petición soap, firmarla y enviar una factura:
Archivo Adjunto 3287

Voy a limpiar un poco el código, añadir comentarios y hacer pruebas con un certificado ya dado de alta en el webservice para comprobar que funciona correctamente, e iré publicando los pasos poco a poco.

Lo que he hecho es lo siguiente. A partir de un proyecto de ejemplo suministrado por los componentes SecureBlackBox, que permite firmar peticiones SOAP (que es lo que nos faltaba), lo he ido adaptando y simplificando para que, a partir de la factura electrónica que creamos en el primer post, añada la firma, cabeceras, etc. requeridas por el webservice.

Hay que hacer dos firmas: la primera, para crear la factura electrónica (primer post), y la segunda para firmar la petición soap. Para ambas firmas usaríamos el mismo certificado. Los componentes SecureBlackBox permiten usar cualquier tipo de certificado (a partir de un .p12, .cer, de los certificados instalados en Windows, etc.).

No he conseguido hacerlo sin los componentes SecureBlackBox, lo siento. Supongo que es posible usar un certificado del almacén de certificados de Windows con código puro y duro, pero no he tenido tiempo de averiguar eso. Además, habría que incrustar al SOAP las cabeceras y toda la información del certificado a mano, y aunque es posible, lo veo una locura. Los SecureBlackBox lo hacen automáticamente.

Intentaré publicar los pasos esta tarde o mañana.

espinete 11-11-2015 13:24:34

Sigo vivo. Llevo esperando una semana respuesta desde face, porque el webservice me devuelve el siguiente error:

100 - La firma de la petición soap no es válida

Así que a esperar.

iMia 11-11-2015 17:43:38

Cita:

Empezado por espinete (Mensaje 498900)
Bueno, creo que lo he conseguido :)

No he conseguido hacerlo sin los componentes SecureBlackBox, lo siento. Supongo que es posible usar un certificado del almacén de certificados de Windows con código puro y duro, pero no he tenido tiempo de averiguar eso. Además, habría que incrustar al SOAP las cabeceras y toda la información del certificado a mano, y aunque es posible, lo veo una locura. Los SecureBlackBox lo hacen automáticamente.

Intentaré publicar los pasos esta tarde o mañana.

Ni lo conseguirás... por desgracia... no hay ninguna librería que firme y que sea free en Delphi...

espinete 19-11-2015 11:18:52

Me he quedado atascado, y la única respuesta que me dan los de soporte del webservice es la misma respuesta que me da el validador online de firma (https://valide.redsara.es/valide/inicio.html):

"Id is not an attribute"

Así que ni idea.

Estoy hablando con el soporte de Eldos, creadores de los SecureBlackBox, a ver si me ayudan a ver qué pasa. Me niego a dejar esto ahora ya que creo que tengo el 90% conseguido, pero la verdad es que hay días que me dan ganas de abandonar y esperar a que pase algo más de tiempo para que haya más información en la red.

Seguiré intentándolo

elguille 27-11-2015 14:19:39

Hola Espinete, despues de mucho tiempo voy a retomar este tema. Cuando lo deje estaba atascado con el error "connection lost" ahora no me da error pero el servicio retorna

<faultcode>500</faultcode>
<faultstring>20151127135337151899 - 300 - El certificado electr&#xF3;nico no est&#xE1; dado de alta en FACe. Para la presentaci&#xF3;n automatizada de facturas es necesario registrarse previamente en https://face.gob.es/es/proveedores</faultstring>

me fijo que he empleado el servicio definido en

https://webservice.face.gob.es/sspp

el cual segun la nueva documentacion es antiguo (aunque esta activo :confused:)

si empleo los nuevos, recibo diferentes errores para la misma peticion soap que antes (que no da error)

STAGING (https://se-face-webservice.redsara.es/facturasspp2) RCP-Literal
Decode from base64 failed

PROD (https://webservice.face.gob.es/facturasspp2) RCP-Literal
Xml parse error at position 1 (0x1)

¿Alguna idea? Gracias

Este el el codigo
Código Delphi [-]
// pruebas con cliente secureblackboc
              FXMLDocument := TElXMLDOMDocument.Create;
              FXMLDocument.LoadFromFile(extractfilepath(application.exename) + 'requestsoap.xml');
              FSOAPClient := TElXMLSOAPClient.Create(nil);
              try
                FSOAPClient.SOAPPrefix := 'soap';
                FSOAPClient.SOAPVersion := SOAP_v1_2;
                FSOAPClient.OperationName := 'enviarFactura';

                FSOAPClient.MessageNamespaces.Clear;
                FSOAPClient.HTTPClient := HTTPSClient;
// entorno pruebas que no funciona
//                FSOAPClient.URL := 'https://se-face-webservice.redsara.es/facturasspp';
// produccion
                FSOAPClient.URL := 'https://webservice.face.gob.es/facturassspp2';
                FSOAPClient.OperationNamespaceURI := 'https://webservice.face.gob.es';
                FSOAPClient.SOAPAction := 'https://webservice.face.gob.es#enviarFactura';

                fsoapclient.GenerateMessage;
                FSOAPClient.XMLDocument.LoadFromFile(extractfilepath(application.exename) + 'requestsoap.xml');
                FSOAPClient.SOAPMessage.LoadFromXML(FSOAPClient.XMLDocument); // reload a SOAP message if needed

                FSOAPClient.SendMessage;
                fsoapclient.ResponseXMLDocument.SaveToFile('resul.xml');

              except
                on E: Exception do
                begin
                  MessageDlg('Failed to send SOAP message: ' + E.Message, mtError, [mbOk], 0);
                end;
              end;

espinete 01-12-2015 16:58:11

Hola, elguille

Lo siento, yo no uso el componente TElXMLSOAPClient. Sólo uso los componentes de SecureBlackBox necesarios para hacer las firmas.
Para enviar la petición SOAP, importo el wsdl y hago la petición tal como indiqué en un post anterior.

Los técnicos de SecureBlackBox están intentando ayudarme a crear/firmar la petición SOAP, pero siempre obtengo "La firma de la petición soap no es válida".

Si existiera UN SOLO EJEMPLO de una petición soap completa para poder comparar sería todo maravilloso, pero en las instrucciones solo hay peticiones sin firmar.

espinete 01-12-2015 19:10:19

Hola de nuevo.

He hecho una mezcla entre mi código y el de elguille, y he usado el webservice nuevo de STAGING para pruebas. PARECE QUE FUNCIONA, pero obtengo un error:

411 - No existe o inactiva la Oficina Contable asociado al código "A05003410"

He usado una oficina contable, unidad tramitadora, etc. al azar. ¿Alguien sabe si puede usarse cualquiera en el entorno de pruebas? ¿O es que he tenido mala suerte y esa no vale?

Al menos ya no obtengo el error 100 (La firma de la petición soap no es válida) ni ningún otro error!!!!

espinete 01-12-2015 19:28:21

Bueno, creo que funciona :):):)

He conseguido obtener el listado de Administraciones, que también requiere una petición soap firmada. No puedo enviar captura de pantalla porque los límites de tamaño y dimensiones para los archivos son un tanto peculiares en el foro... :rolleyes:

Mañana intentaré enviar una factura (necesitaría saber qué código de administración es válido) y poner el código que he usado

elguille 10-12-2015 12:02:45

Espinete, ¿lo has conseguido? ¿Puedes ilustrarnos?

Gracias

espinete 10-12-2015 19:38:59

Hola!

Pues sí, creo que lo tengo todo, a falta de probar en modo producción.
Esta semana ha habido varios cortes del servicio (avisan por email cuando tienen previsto uno), pero conseguí hacer pruebas para enviar una factura, consultar el estado de una factura, obtener el listado de organismos disponibles, anular una factura, etc.

Mañana intentaré poner algunos ejemplos, pero ahora mismo tengo este a mano: Consultar Administraciones:

Código Delphi [-]
procedure TfrmMain.Button3Click(Sender: TObject);
var
    answ : ConsultarAdministracionesResponse;
    htpr: THTTPRIO;
    sphdr: TSOAPHEADER;
    WS: FacturaSSPPWebServiceProxyPort;
    UnidadDir : UnidadDir3;
    i:integer;
begin
    if comboCertificate.ItemIndex=-1 then
    begin
      showmessage('Debe seleccionar primero un certificado en el desplegable.');
      exit;
    end;
    
    Listbox1.items.clear;

        //Creamos los headers, necesarios para la petición SOAP
        sphdr := TSOAPHEADER.create;

        // Creo el HTTP, que utilizaré como vehículo
        htpr := THTTPRIO.create(Self);

        //Aquí trabajamos con el propio httprio, donde podemos poner cabeceras, asignar eventos, etc...

        htpr.SOAPHeaders.Send(sphdr);
        htpr.OnBeforeExecute := HTTPRIO1BeforeExecute;

        // Al crear la instancia del WS, le asigno el httprio que he creado y he manipulado...
        WS:= GetFacturaSSPPWebServiceProxyPort(FALSE, '', htpr);
        try
            answ := WS.consultarAdministraciones;
        except
            on e:exception do showmessage(e.Message);
        end;

        if answ.resultado.codigo='0' then
        begin
            UnidadDir := UnidadDir3.Create;
            for i:=0 to length(answ.administraciones)-1 do
            begin
                UnidadDir := answ.administraciones[i];
                begin
                    listbox1.items.append(UnidadDir.codigo + ' - ' + UnidadDir.nombre )
                end;
            end;
            UnidadDir.Free;
        end;
end;

El evento HTTPRIO1BeforeExecute creo recordar que ya lo publiqué en otro post más arriba.
Cuando tenga más tiempo editaré el post original añadiendo todas las funciones.


La franja horaria es GMT +2. Ahora son las 14:28:15.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi