EldoS | Feel safer!

Software components for data protection, secure storage and transfer

XML Signature KeyInfo

Also by EldoS: Rethync
The cross-platform framework that simplifies synchronizing data between mobile and desktop applications and servers and cloud storages
#11004
Posted: 09/10/2009 06:11:54
by Datadec Online (Basic support level)
Joined: 09/09/2009
Posts: 29

Hi,

We are having probles with keyinfo node. We need a node like this <ds:KeyInfo Id="KeyInfo"> but the example you provide generates only <ds:KeyInfo> without the Id. We have seen the possibility of giving a name to the KeyInfo but we need to generate and Id too.

Is this possible?

Thanks, regards
Javier Gutiérrez
#11005
Posted: 09/10/2009 06:34:41
by Santiago Castaño (Standard support level)
Joined: 04/16/2006
Posts: 155

Yes of course, just use:

Referencia_C is a string containing for example "Certificate1" that i know that is what you're trying to get to get a Facturae (XAdES from Spain)

Signer.Signature.KeyInfo.ID := Referencia_C;

After the .Sign call.

Regards,
#11006
Posted: 09/10/2009 06:44:34
by Datadec Online (Basic support level)
Joined: 09/09/2009
Posts: 29

Thanks Santiago,

Yes, I have added the line Signer.Signature.KeyInfo.ID = "KeyInfo" after .Sing call and now the node appears correctly. Instead of the still have problems in order to the signed xades xml document was recognized by the validator from ministry. We noticed that there is a section that not appears and seems to be required.

<ds:Reference URI="#KeyInfo">
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>s3jW39IxLIX5v8RVHkrMS7TesMQ=</ds:DigestValue>
</ds:Reference>

We have added a Reference manuallt by the example provided by SBB but when signing throws the following error: "Reference require context." at this line: Signer.UpdateReferencesDigest()

Do you have any example of signing a facturae xml document?

Thanks, regards,
Javier Gutiérrez
#11007
Posted: 09/10/2009 06:46:52
by Dmytro Bogatskyy (EldoS Corp.)

#11008
Posted: 09/10/2009 06:54:48
by Santiago Castaño (Standard support level)
Joined: 04/16/2006
Posts: 155

Javier, for your issue, just use:

Ref2 := TElXMLReference.Create;
Ref2.URI := '#'+Referencia_C;
Signer.References.Add(Ref2);

After the Signer.UpdateReferencesDigest; but before the .sign.
#11009
Posted: 09/10/2009 07:03:43
by Datadec Online (Basic support level)
Joined: 09/09/2009
Posts: 29

Ok, It works fine!

Now the facturae document seems to be well formed.

Thaks!
#11011
Posted: 09/10/2009 07:18:45
by Santiago Castaño (Standard support level)
Joined: 04/16/2006
Posts: 155

No problem ;)
#12136
Posted: 01/20/2010 09:26:23
by Sergio Hernandez (Standard support level)
Joined: 03/03/2008
Posts: 14

Hi Santiago, I also write from Spain and also try to sign Facturae: There is still no example code anywere?

I am having problems with it, as Datadec had, and the facturae site is not very helpfull.

If no code is available, I promise to publish my *crappy* one once it is able to sign Facturae correctly!
#12141
Posted: 01/20/2010 10:07:33
by Santiago Castaño (Standard support level)
Joined: 04/16/2006
Posts: 155

Hi Sergio,

What problem are you having? is it the keyinfo node? you can see previous posts in this thread to solve them, if you need to sign with more than once certificate, then you need to make extra calculations to get Certificate1, Certificate2, ...

For example, a code to make this is:

Referencia_C:=Buscar_Index_Cer(FXMLDocument); // (calculated ASAP after loading FXMLDocument)

where

function Buscar_Index_Cer(XMLDocument:TElXMLDOMDocument):String;
var IdIndex: Integer;
begin
Result:='Certificate1';
try
IdIndex := 1;
while Assigned(FindElementById(XMLDocument.DocumentElement, Format('Certificate%d',[IdIndex]))) do
Inc(IdIndex);
Result:=Format('Certificate%d',[IdIndex]);
except
Result:='Certificate1';
end;
end;

Hope that gives you the hints to accomplish it with also the previous posts...
#12431
Posted: 02/15/2010 08:03:29
by Sergio Hernandez (Standard support level)
Joined: 03/03/2008
Posts: 14

Hi Santiago, thanx a lot for the piece of code, but this particular point is not neccesary (I think)!

I also had concerns about this point but:

1.- FacturaE doesn't ask you to use "Certificate-1" in any part of the documentation, so you don't need to adjust those node names at all.

2.- The same happends to "Signature-1", you are free to use any other format, so I left SBB to chose one for me, and it validates ok (you don't break any given rule).

3.- Validator doesn't support a file with more than 1 signeture, not at all at least in my test, so i don't really bother about trying to validate my 2-times-signed files at all.

So, if you don't use a fixed Signature name, you can repeat on all your singatures the same Certificate-1 name! It is as I do, and there is no problem as each one "lives" under a diferente named signature node (you can't have same signature name anyway).

If I am mistaken, please advise!

Anyway, I finally got my code to sign ok with version V3.1 of signing (and V3.2 of FacturaE btw), validating no the www.facturae.es site, here I copy all the code (there is only a call to a function to convert from hex text to ByteArray that SBB team sent to me).

Hope it is of help to others, as the info on FacturaE site is... not very reliable (don't trust on the digest they have for the PDF about signing V3.1, it is not its RSA digest that is there).

It is VERY commented, and may be the comments are not 100% accurate technically (XML is new to me) but I prefered to place there all my "findings" in the long way to sign those FacturaEs.

Code
      // ************************
      // ** Firmar fichero XML **
      // ************************
      //
      //Usamos FacturaE politica de firma V3.1:
      //http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf
      //
      //Creo objetos involucrados en la firma...
      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 firmador en XML...
      XML_XAdES:=   TElXAdESSigner.Create(nil);          //Subjeto firmador 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&#225; luego pues los digest no
        // deber&#237;an incluir estos CR+LF).
        XML_Doc.LoadFromStream(MS, '', true);
        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;
        // Quiero firma electronica, no claves secretas (MAC)
        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&#233; 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&#237;tica de firma electr&#243;nica para facturaci&#243;n electr&#243;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:
        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&#237;tica de firmado a Base64.');
          XML_Buf[j]:= k1 shl 4 + k2;
        end;
        XML_Signer.XAdESProcessor.PolicyId.SigPolicyHash.DigestValue:= XML_Buf;
        //Anoto la fecha y hora del firmado (del PC, no es legalmente valida)
        XML_Signer.XAdESProcessor.SigningTime := UtcNow;
        //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 &#191;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;
        for i:= 0 to Cajon.Count-1 do begin
          if Cajon.Certificates[i].PrivateKeyExists then begin
            XML_KeyData.certificate:= Cajon.Certificates[i];
            break;
          end;
        end;
        //Compruebo que ha quedado alg&#250;n certificado v&#225;lido...
        if not Assigned(XML_KeyData.certificate) then
          raise Exception.create('FirmaXML: No se carg&#243; uin certificado v&#225;lido para firmar, no contiene clave privada.');
        XML_Signer.KeyData:= XML_KeyData;
        //A&#241;ado a la lista de nodos a firmar
        //==================================
        //NOTA: El metodo SHA1 no es el mas seguro, pero el SHA2 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&#237;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&#241;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.
        MS.Position:= 0;
        XML_Doc.SaveToStream(MS);
        result:= true;
        //Guardo XML firmado a fichero
        //============================
        Out_Stream:= TFileStream.create(SAveDialog1.FileName, fmCreate);
        XML_Doc.SaveToStream(Out_Stream);
        FreeAndNil(Out_Stream);
Also by EldoS: Callback File System
Create virtual file systems and disks, expose and manage remote data as if they were files on the local disk.

Reply

Statistics

Topic viewed 13759 times

Number of guests: 3, registered members: 0, in total hidden: 0




|

Back to top

As of July 15, 2016 EldoS Corporation will operate as a division of /n software inc. For more information, please read the announcement.

Got it!