case TipoFirma of
tfXML: //Fichero XML para FacturaE (con politica de firmado V3.1)
begin
// ************************
// ** 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á luego pues los digest no
// deberí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é 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 [url=http://www.facturae.es]www.facturae.es[/url] 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:= '[url=http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf';]http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf';[/url]
//NOTA: Si vas a la web, este PDF esta en otro link diferente (WTF):
//'[url=http://www.facturae.es/es-ES/Documentacion/Politicas/Politicas/Versi%C3%B3n%203_1/Politica_Firma_formato_facturae_v3_1.pdf';]http://www.facturae.es/es-ES/Documentacion/Politicas/Politicas/Versi%C3%B3n%203_1/Politica_Firma_formato_facturae_v3_1.pdf';[/url]
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:= '[url=http://www.w3.org/2000/09/xmldsig#sha1';]http://www.w3.org/2000/09/xmldsig#sha1';[/url]
//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;
//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 ¿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ú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.
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);
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; //XML