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.