SecureBlackbox 16: Signing invoices for the Spanish government in the factura format
Note: This article applies only to SecureBlackbox Legacy. For future development please consider using the latest version.
This entry provides sample code to sign electronic documents (such as invoices for the Spanish government) in the factura XML format.
The resulting signature is the enveloped signature, which contains a reference to the Document element, signed KeyInfo element, and XAdES-EPES form.
Signed XML Sample
Below is an example of the signed XML generated by the code example:
<fe:Facturae xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:fe="http://www.facturae.es/Facturae/2014/v3.2.1/Facturae"> ... <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-998668816"> <ds:SignedInfo> <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> <ds:Reference Id="Ref1" URI=""> <ds:Transforms> <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> </ds:Transforms> <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <ds:DigestValue>rKxz...</ds:DigestValue> </ds:Reference> <ds:Reference URI="#Certificate1"> <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <ds:DigestValue>pLvS...</ds:DigestValue> </ds:Reference> <ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#SignedProperties-292532795"> <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <ds:DigestValue>6tQA...</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>PVoD...</ds:SignatureValue> <ds:KeyInfo Id="Certificate1"> <ds:KeyValue> <ds:RSAKeyValue> <ds:Modulus>We0Z...</ds:Modulus> <ds:Exponent>AQAB</ds:Exponent> </ds:RSAKeyValue> </ds:KeyValue> <ds:X509Data> <ds:X509Certificate>MIIC...</ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> <ds:Object> <etsi:QualifyingProperties xmlns:etsi="http://uri.etsi.org/01903/v1.3.2#" Target="#Signature-998668816"> <etsi:SignedProperties Id="SignedProperties-292532795"> <etsi:SignedSignatureProperties> <etsi:SigningTime>2016-01-01T00:00:00.000Z</etsi:SigningTime> <etsi:SigningCertificate> <etsi:Cert> <etsi:CertDigest> <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <ds:DigestValue>Qs3C...</ds:DigestValue> </etsi:CertDigest> <etsi:IssuerSerial> <ds:X509IssuerName>CN=...</ds:X509IssuerName> <ds:X509SerialNumber>26...</ds:X509SerialNumber> </etsi:IssuerSerial> </etsi:Cert> </etsi:SigningCertificate> <etsi:SignaturePolicyIdentifier> <etsi:SignaturePolicyId> <etsi:SigPolicyId> <etsi:Identifier>http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf</etsi:Identifier> <etsi:Description>Política de Firma FacturaE v3.1</etsi:Description> </etsi:SigPolicyId> <etsi:SigPolicyHash> <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <ds:DigestValue>Ohixl6upD6av8N7pEvDABhEL6hM=</ds:DigestValue> </etsi:SigPolicyHash> </etsi:SignaturePolicyId> </etsi:SignaturePolicyIdentifier> <etsi:SignerRole> <etsi:ClaimedRoles> <etsi:ClaimedRole>emisor</etsi:ClaimedRole> </etsi:ClaimedRoles> </etsi:SignerRole> </etsi:SignedSignatureProperties> <etsi:SignedDataObjectProperties> <etsi:DataObjectFormat ObjectReference="#Ref1"> <etsi:Description>Factura electrónica</etsi:Description> <etsi:MimeType>text/xml</etsi:MimeType> </etsi:DataObjectFormat> </etsi:SignedDataObjectProperties> </etsi:SignedProperties> </etsi:QualifyingProperties> </ds:Object> </ds:Signature> </fe:Facturae>
Code Example
Below are code examples in C#, Delphi, and PHP, several of the platforms supported by SecureBlackbox.
C#:
void SignFactura(TElXMLDOMDocument xmlDocument, TElX509Certificate cert) { TElXMLSigner Signer = new TElXMLSigner(); TElXAdESSigner XAdESSigner = new TElXAdESSigner(); TElXMLKeyInfoX509Data X509KeyInfoData = new TElXMLKeyInfoX509Data(false); try { Signer.XAdESProcessor = XAdESSigner; Signer.SignatureMethodType = SBXMLSec.Unit.xmtSig; Signer.SignatureMethod = SBXMLSec.Unit.xsmRSA_SHA1; Signer.CanonicalizationMethod = SBXMLDefs.Unit.xcmCanon; Signer.IncludeKey = true; int k = Signer.References.Add(); TElXMLReference Ref = Signer.References[k]; Ref.DigestMethod = SBXMLSec.Unit.xdmSHA1; Ref.ID = "Ref1"; Ref.URI = ""; Ref.URINode = xmlDocument.DocumentElement; Ref.TransformChain.AddEnvelopedSignatureTransform(); Signer.UpdateReferencesDigest(); k = Signer.References.Add(); Ref = Signer.References[k]; Ref.DigestMethod = SBXMLSec.Unit.xdmSHA1; Ref.URI = "#Certificate1"; XAdESSigner.XAdESVersion = SBXMLAdES.Unit.XAdES_v1_3_2; XAdESSigner.Included = SBXMLAdESIntf.Unit.xipSignerRole; XAdESSigner.SigningTime = DateTime.UtcNow; XAdESSigner.SignerRole.ClaimedRoles.AddText(XAdESSigner.XAdESVersion, xmlDocument, "emisor"); string URL = "http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf"; XAdESSigner.PolicyId.SigPolicyId.Identifier = URL; XAdESSigner.PolicyId.SigPolicyId.IdentifierQualifier = SBXMLAdES.Unit.xqtNone; XAdESSigner.PolicyId.SigPolicyId.Description = "Política de Firma FacturaE v3.1"; XAdESSigner.PolicyId.SigPolicyHash.DigestMethod = SBXMLSec.Unit.DigestMethodToURI(SBXMLSec.Unit.xdmSHA1); // uncomment to calculate a digest value or use precalculated value //Buf := DownloadData(URL); //XAdESSigner.PolicyId.SigPolicyHash.DigestValue := CalculateDigest(@Buf[0], Length(Buf), xdmSHA1); XAdESSigner.PolicyId.SigPolicyHash.DigestValue = SBXMLUtils.Unit.ConvertFromBase64String("Ohixl6upD6av8N7pEvDABhEL6hM="); XAdESSigner.SigningCertificates = new TElMemoryCertStorage(); XAdESSigner.OwnSigningCertificates = true; XAdESSigner.SigningCertificates.Add(cert); XAdESSigner.Generate(SBXMLAdES.Unit.XAdES_EPES); XAdESSigner.QualifyingProperties.XAdESPrefix = "etsi"; TElXMLDataObjectFormat DataFormat = new TElXMLDataObjectFormat(XAdESSigner.XAdESVersion); DataFormat.Description = "Factura electrónica"; DataFormat.MimeType = "text/xml"; DataFormat.ObjectReference = "#Ref1"; XAdESSigner.QualifyingProperties.SignedProperties.SignedDataObjectProperties.DataObjectFormats.Add(DataFormat); X509KeyInfoData.IncludeKeyValue = true; X509KeyInfoData.IncludeDataParams = SBXMLSec.Unit.xkidX509Certificate; X509KeyInfoData.Certificate = cert; Signer.KeyData = X509KeyInfoData; Signer.GenerateSignature(); Signer.Signature.KeyInfo.ID = "Certificate1"; Signer.SaveEnveloped(xmlDocument.DocumentElement); } finally { X509KeyInfoData.Dispose(); Signer.Dispose(); XAdESSigner.Dispose(); } }
PHP:
function SignFactura($xmlDocument, $cert) { $Signer = new TElXMLSigner(NULL); $XAdESSigner = new TElXAdESSigner(NULL); $X509KeyInfoData = new TElXMLKeyInfoX509Data(false); $SigningCertificates = new TElMemoryCertStorage(NULL); $Signer->XAdESProcessor = $XAdESSigner; $Signer->SignatureMethodType = TElXMLSigMethodType::xmtSig; $Signer->SignatureMethod = TElXMLSignatureMethod::xsmRSA_SHA1; $Signer->CanonicalizationMethod = TElXMLCanonicalizationMethod::xcmCanon; $Signer->IncludeKey = true; $k = $Signer->References->Add(); $Ref = $Signer->References->get_Reference($k); $Ref->DigestMethod = TElXMLDigestMethod::xdmSHA1; $Ref->ID = 'Ref1'; $Ref->URI = ''; $Ref->URINode = $xmlDocument->DocumentElement; $Ref->TransformChain->AddEnvelopedSignatureTransform(); $Signer->UpdateReferencesDigest(); $k = $Signer->References->Add(); $Ref = $Signer->References->get_Reference($k); $Ref->DigestMethod = TElXMLDigestMethod::xdmSHA1; $Ref->URI = '#Certificate1'; $XAdESSigner->XAdESVersion = TSBXAdESVersion::XAdES_v1_3_2; $XAdESSigner->Included = TElXAdESIncludedProperties::xipSignerRole; $XAdESSigner->SigningTime = SBUtils\UTCNow(); $XAdESSigner->SignerRole->ClaimedRoles->AddText($XAdESSigner->XAdESVersion, $xmlDocument, 'emisor'); $URL = 'http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf'; $XAdESSigner->PolicyId->SigPolicyId->Identifier = $URL; $XAdESSigner->PolicyId->SigPolicyId->IdentifierQualifier = TSBXAdESQualifierType::xqtNone; $XAdESSigner->PolicyId->SigPolicyId->Description = 'Politica de Firma FacturaE v3.1'; $XAdESSigner->PolicyId->SigPolicyHash->DigestMethod = SBXMLSec\DigestMethodToURI(TElXMLDigestMethod::xdmSHA1); // uncomment to calculate a digest value or use precalculated value //$Buf = DownloadData($URL); //$XAdESSigner->PolicyId->SigPolicyHash->DigestValue = SBXMLSec\CalculateDigest(Buf, Length(Buf), TElXMLDigestMethod::xdmSHA1); $XAdESSigner->PolicyId->SigPolicyHash->DigestValue = SBXMLUtils\ConvertFromBase64String('Ohixl6upD6av8N7pEvDABhEL6hM='); $SigningCertificates->Add($cert, false); $XAdESSigner->SigningCertificates = $SigningCertificates; $XAdESSigner->Generate(TSBXAdESForm::XAdES_EPES); $XAdESSigner->QualifyingProperties->XAdESPrefix = 'etsi'; $DataFormat = new TElXMLDataObjectFormat($XAdESSigner->XAdESVersion); $DataFormat->Description = 'Factura electronica'; $DataFormat->MimeType = 'text/xml'; $DataFormat->ObjectReference = '#Ref1'; $XAdESSigner->QualifyingProperties->SignedProperties->SignedDataObjectProperties->DataObjectFormats->Add($DataFormat); $DataFormat->detachHandle(); $X509KeyInfoData->IncludeKeyValue = true; $X509KeyInfoData->IncludeDataParams = TElXMLKeyInfoX509DataParams::xkidX509Certificate; $X509KeyInfoData->Certificate = $cert; $Signer->KeyData = $X509KeyInfoData; $Signer->GenerateSignature(); $Signer->Signature->KeyInfo->ID = 'Certificate1'; $Signer->SaveEnveloped($xmlDocument->DocumentElement); }
Delphi:
procedure SignFactura(XMLDocument : TElXMLDOMDocument; Cert : TElX509Certificate); var Signer : TElXMLSigner; XAdESSigner: TElXAdESSigner; X509KeyInfoData: TElXMLKeyInfoX509Data; DataFormat : TElXMLDataObjectFormat; Ref : TElXMLReference; URL : string; Buf : ByteArray; k : Integer; begin Signer := TElXMLSigner.Create(nil); XAdESSigner := TElXAdESSigner.Create(nil); X509KeyInfoData := TElXMLKeyInfoX509Data.Create(false); try Signer.XAdESProcessor := XAdESSigner; Signer.SignatureMethodType := xmtSig; Signer.SignatureMethod := xsmRSA_SHA1; Signer.CanonicalizationMethod := xcmCanon; Signer.IncludeKey := true; k := Signer.References.Add; Ref := Signer.References[k]; Ref.DigestMethod := xdmSHA1; Ref.ID := 'Ref1'; Ref.URI := ''; Ref.URINode := XMLDocument.DocumentElement; Ref.TransformChain.AddEnvelopedSignatureTransform(); Signer.UpdateReferencesDigest(); k := Signer.References.Add; Ref := Signer.References[k]; Ref.DigestMethod := xdmSHA1; Ref.URI := '#Certificate1'; XAdESSigner.XAdESVersion := XAdES_v1_3_2; XAdESSigner.Included := [xipSignerRole]; XAdESSigner.SigningTime := UTCNow; XAdESSigner.SignerRole.ClaimedRoles.AddText(XAdESSigner.XAdESVersion, XMLDocument, 'emisor'); URL := 'http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf'; XAdESSigner.PolicyId.SigPolicyId.Identifier := URL; XAdESSigner.PolicyId.SigPolicyId.IdentifierQualifier := xqtNone; XAdESSigner.PolicyId.SigPolicyId.Description := 'Política de Firma FacturaE v3.1'; XAdESSigner.PolicyId.SigPolicyHash.DigestMethod := DigestMethodToURI(xdmSHA1); // uncomment to calculate a digest value or use precalculated value //Buf := DownloadData(URL); //XAdESSigner.PolicyId.SigPolicyHash.DigestValue := CalculateDigest(@Buf[0], Length(Buf), xdmSHA1); XAdESSigner.PolicyId.SigPolicyHash.DigestValue := ConvertFromBase64String('Ohixl6upD6av8N7pEvDABhEL6hM='); XAdESSigner.SigningCertificates := TElMemoryCertStorage.Create(nil); XAdESSigner.OwnSigningCertificates := true; XAdESSigner.SigningCertificates.Add(Cert); XAdESSigner.Generate(XAdES_EPES); XAdESSigner.QualifyingProperties.XAdESPrefix := 'etsi'; DataFormat := TElXMLDataObjectFormat.Create(XAdESSigner.XAdESVersion); DataFormat.Description := 'Factura electrónica'; DataFormat.MimeType := 'text/xml'; DataFormat.ObjectReference := '#Ref1'; XAdESSigner.QualifyingProperties.SignedProperties.SignedDataObjectProperties.DataObjectFormats.Add(DataFormat); X509KeyInfoData.IncludeKeyValue := true; X509KeyInfoData.IncludeDataParams := [xkidX509Certificate]; X509KeyInfoData.Certificate := Cert; Signer.KeyData := X509KeyInfoData; Signer.GenerateSignature; Signer.Signature.KeyInfo.ID := 'Certificate1'; Signer.SaveEnveloped(XMLDocument.DocumentElement); finally FreeAndNil(X509KeyInfoData); FreeAndNil(Signer); FreeAndNil(XAdESSigner); end; end;
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at kb@nsoftware.com.