Signature Policy in XAdES Signed SOAP Message for AEAT Spain

Let's break down how a XAdES Signature Policy is added to an XML Signature specifically for the context of SOAP messages sent to the AEAT (Agencia Estatal de Administración Tributaria), the Spanish Tax Agency.

Note: This page describes the details, but if desired you can skip ahead to the Example to see how to use Chilkat Online Tools to generate application source code in your programming language.

Overview: What is a Signature Policy and Why Does AEAT Require It?

A Signature Policy is a set of rules that an electronic signature must follow to be considered valid for a specific purpose or by a particular entity. For the AEAT, this is a crucial component. By including a Signature Policy Identifier in your signature, you are explicitly stating:

  1. Compliance - This signature was created following the exact rules defined in the AEAT's official policy document.
  2. Legal Validity - It provides a clear legal and technical framework, ensuring the signature's integrity, authenticity, and non-repudiation according to Spanish and EU regulations (like eIDAS).
  3. Interoperability - It guarantees that the AEAT's systems can automatically process and validate the signature without ambiguity.

The specific format used is XAdES-EPES (Explicit Policy-based Electronic Signature), which means the policy identifier is embedded directly within the signature's properties.


How the Signature Policy is Added: Step-by-Step

Adding the policy isn't a single step but a process of constructing a XAdES signature correctly within the SOAP message's security header.

Step 1: The Foundation - A Standard XML Signature (XMLDSig)

You start with a standard World Wide Web Consortium (W3C) XML Signature, which is placed inside the <wsse:Security> header of a SOAP message. This signature must sign the content of the <soap:Body>.

Step 2: Introducing XAdES - The <xades:QualifyingProperties> Element

To upgrade the standard XML Signature to a XAdES signature, you add a <ds:Object> element inside your <ds:Signature>. This <ds:Object> will contain all the advanced XAdES information within a root element called <xades:QualifyingProperties>.

<ds:Signature>
  ...
  <ds:Object>
    <xades:QualifyingProperties Target="#SignatureID">
      <!-- All XAdES properties go here -->
    </xades:QualifyingProperties>
  </ds:Object>
</ds:Signature>
Step 3: Adding Signed Properties

XAdES distinguishes between properties that are signed (and thus cryptographically protected) and those that are not. The Signature Policy must be a signed property. Therefore, you add <xades:SignedProperties>, which will contain the policy identifier.

<xades:QualifyingProperties Target="#SignatureID">
  <xades:SignedProperties Id="SignedPropertiesID">
    <xades:SignedSignatureProperties>
      <!-- Policy, Signing Time, Certificate info, etc. go here -->
    </xades:SignedSignatureProperties>
  </xades:SignedProperties>
</xades:QualifyingProperties>
  • Id="SignedPropertiesID": This Id is critical. It allows the SignedProperties block itself to be referenced and included in the signature calculation, ensuring its integrity.
Step 4: Adding the <xades:SignaturePolicyIdentifier> Element

This is the core step. Inside <xades:SignedSignatureProperties>, you add the <xades:SignaturePolicyIdentifier> element. This element explicitly declares which policy is being followed.

It has two main parts:

  1. <xades:SignaturePolicyId>: Contains the official identifier and a description.
    • <xades:Identifier>: This holds the Object Identifier (OID) for the specific AEAT policy. For FacturaE 3.2.1 / AEAT v3.1, the OID is urn:oid:2.16.724.1.1.2.1.2.6.
    • <xades:Description>: A human-readable text, like Política de Firma de la AEAT v3.1.
  2. <xades:SigPolicyHash>: Contains the hash of the official policy document. This proves that you are referencing the exact, unaltered version of the policy.
    • <ds:DigestMethod>: Specifies the hash algorithm (e.g., SHA-256).
    • <ds:DigestValue>: The Base64 encoded hash value of the policy document.
Step 5: Referencing the Signed Properties

For the signature to be valid, the <xades:SignedProperties> block must be signed along with the SOAP Body. This is achieved by adding a special <ds:Reference> inside the <ds:SignedInfo> block that points to the Id of the <xades:SignedProperties> element.

This reference must have a specific Type attribute: http://uri.etsi.org/01903#SignedProperties.

<ds:SignedInfo>
  ...
  <!-- Reference to the SOAP Body -->
  <ds:Reference URI="#BodyId">
    ...
  </ds:Reference>
  
  <!-- CRITICAL: Reference to the XAdES Signed Properties -->
  <ds:Reference URI="#SignedPropertiesID" Type="http://uri.etsi.org/01903#SignedProperties">
    <ds:Transforms>
      <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
    </ds:Transforms>
    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
    <ds:DigestValue>...</ds:DigestValue>
  </ds:Reference>
</ds:SignedInfo>

Example

Here is a simplified example of a SOAP message with a XAdES signature containing the AEAT Signature Policy. Key parts are highlighted with comments.

You can copy and paste the following XML into Chilkat's online tool for generating code to produce the given signed XML.

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <soap:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1">
      <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-123">
        <ds:SignedInfo>
          <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
          <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
          
          <!-- 1. Reference to the SOAP Body being signed -->
          <ds:Reference URI="#Body-456">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>...</ds:DigestValue> <!-- Digest of the Body -->
          </ds:Reference>
          <!-- 2. Reference to the XAdES properties, which includes the policy -->
          <ds:Reference URI="#SignedProperties-789" Type="http://uri.etsi.org/01903#SignedProperties">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>...</ds:DigestValue> <!-- Digest of the SignedProperties block -->
          </ds:Reference>
        </ds:SignedInfo>
        
        <ds:SignatureValue>...</ds:SignatureValue> <!-- The actual signature value -->
        
        <ds:KeyInfo>
          <wsse:SecurityTokenReference>
            <wsse:X509Data>
              <wsse:X509Certificate>...</wsse:X509Certificate> <!-- Your public certificate -->
            </wsse:X509Data>
          </wsse:SecurityTokenReference>
        </ds:KeyInfo>
        <!-- 3. The XAdES Object containing the policy -->
        <ds:Object>
          <xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Target="#Signature-123">
            <xades:SignedProperties Id="SignedProperties-789">
              <xades:SignedSignatureProperties>
                <xades:SigningTime>2023-10-27T10:30:00Z</xades:SigningTime>
                <xades:SigningCertificate>
                  <!-- ... Certificate details ... -->
                </xades:SigningCertificate>
                
                <!-- 4. THIS IS THE SIGNATURE POLICY BLOCK -->
                <xades:SignaturePolicyIdentifier>
                  <xades:SignaturePolicyId>
                    <xades:SigPolicyId>
                      <xades:Identifier>urn:oid:2.16.724.1.1.2.1.2.6</xades:Identifier>
                      <xades:Description>Política de Firma de la AEAT v3.1</xades:Description>
                    </xades:SigPolicyId>
                    <xades:SigPolicyHash>
                      <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                      <!-- The hash value of the policy document (FacturaE v3.1) -->
                      <ds:DigestValue>Vjsi9p+4p68+PYy2d2qgB01n83v3a43Q2Xy9gT4sT2s=</ds:DigestValue>
                    </xades:SigPolicyHash>
                  </xades:SignaturePolicyId>
                </xades:SignaturePolicyIdentifier>
                
              </xades:SignedSignatureProperties>
            </xades:SignedProperties>
          </xades:QualifyingProperties>
        </ds:Object>
      </ds:Signature>
    </wsse:Security>
  </soap:Header>
  
  <soap:Body wsu:Id="Body-456">
    <!-- Your AEAT-specific SOAP Body content goes here -->
    <!-- e.g., <ns:SuministroLR>...</ns:SuministroLR> -->
  </soap:Body>
</soap:Envelope>

Summary of Key Points in the Example:

  • The <ds:Signature> signs two things: the <soap:Body> (URI=#Body-456) and the <xades:SignedProperties> block (URI=#SignedProperties-789).
  • The <xades:SignedProperties> block contains the crucial <xades:SignaturePolicyIdentifier>.
  • The identifier uses the official AEAT OID (urn:oid:2.16.724.1.1.2.1.2.6).
  • It includes the hash of the policy document itself to prevent ambiguity.
  • All these components are linked together with Id attributes and URI references to form a compliant XAdES-EPES signature.

Chilkat Online Tool's C# Code for the XML Above:


bool success = true;
// Load the XML to be signed from a file...
Chilkat.Xml xmlToSign = new Chilkat.Xml();
xmlToSign.LoadXmlFile("xmlToSign.xml");

Chilkat.XmlDSigGen gen = new Chilkat.XmlDSigGen();

gen.SigLocation = "soap:Envelope|soap:Header|wsse:Security";
gen.SigLocationMod = 0;
gen.SigId = "Signature-123";
gen.SigNamespacePrefix = "ds";
gen.SigNamespaceUri = "http://www.w3.org/2000/09/xmldsig#";
gen.SignedInfoCanonAlg = "EXCL_C14N";
gen.SignedInfoDigestMethod = "sha256";

// Create an Object to be added to the Signature.
Chilkat.Xml object1 = new Chilkat.Xml();
object1.Tag = "xades:QualifyingProperties";
object1.AddAttribute("xmlns:xades","http://uri.etsi.org/01903/v1.3.2#");
object1.AddAttribute("Target","#Signature-123");
object1.UpdateAttrAt("xades:SignedProperties",true,"Id","SignedProperties-789");
object1.UpdateChildContent("xades:SignedProperties|xades:SignedSignatureProperties|xades:SigningTime","TO BE GENERATED BY CHILKAT");
object1.UpdateChildContent("xades:SignedProperties|xades:SignedSignatureProperties|xades:SigningCertificate","");
object1.UpdateChildContent("xades:SignedProperties|xades:SignedSignatureProperties|xades:SignaturePolicyIdentifier|xades:SignaturePolicyId|xades:SigPolicyId|xades:Identifier","urn:oid:2.16.724.1.1.2.1.2.6");
object1.UpdateChildContent("xades:SignedProperties|xades:SignedSignatureProperties|xades:SignaturePolicyIdentifier|xades:SignaturePolicyId|xades:SigPolicyId|xades:Description","Política de Firma de la AEAT v3.1");
object1.UpdateAttrAt("xades:SignedProperties|xades:SignedSignatureProperties|xades:SignaturePolicyIdentifier|xades:SignaturePolicyId|xades:SigPolicyHash|ds:DigestMethod",true,"Algorithm","http://www.w3.org/2001/04/xmlenc#sha256");
object1.UpdateChildContent("xades:SignedProperties|xades:SignedSignatureProperties|xades:SignaturePolicyIdentifier|xades:SignaturePolicyId|xades:SigPolicyHash|ds:DigestValue","Vjsi9p+4p68+PYy2d2qgB01n83v3a43Q2Xy9gT4sT2s=");

gen.AddObject("",object1.GetXml(),"","");

// -------- Reference 1 --------
gen.AddSameDocRef("Body-456","sha256","EXCL_C14N","","");

// -------- Reference 2 --------
gen.AddObjectRef("SignedProperties-789","sha256","EXCL_C14N","","http://uri.etsi.org/01903#SignedProperties");

// Provide a certificate + private key. (PFX password is test123)
Chilkat.Cert cert = new Chilkat.Cert();
success = cert.LoadPfxFile("qa_data/pfx/cert_test123.pfx","test123");
if (success != true) {
    Debug.WriteLine(cert.LastErrorText);
    return;
}

gen.SetX509Cert(cert,true);

gen.KeyInfoType = "Custom";

// Create the custom KeyInfo XML..
Chilkat.Xml xmlCustomKeyInfo = new Chilkat.Xml();
xmlCustomKeyInfo.Tag = "wsse:SecurityTokenReference";
xmlCustomKeyInfo.UpdateChildContent("wsse:X509Data|wsse:X509Certificate","...");

xmlCustomKeyInfo.EmitXmlDecl = false;
gen.CustomKeyInfoXml = xmlCustomKeyInfo.GetXml();

// Load XML to be signed...
Chilkat.StringBuilder sbXml = new Chilkat.StringBuilder();
xmlToSign.GetXmlSb(sbXml);

gen.Behaviors = "CompactSignedXml";

// Sign the XML...
success = gen.CreateXmlDSigSb(sbXml);
if (success != true) {
    Debug.WriteLine(gen.LastErrorText);
    return;
}

// -----------------------------------------------

// Save the signed XML to a file.
success = sbXml.WriteFile("c:/temp/qa_output/signedXml.xml","utf-8",false);

Debug.WriteLine(sbXml.GetAsString());

// ----------------------------------------
// Verify the signatures we just produced...
Chilkat.XmlDSig verifier = new Chilkat.XmlDSig();
success = verifier.LoadSignatureSb(sbXml);
if (success != true) {
    Debug.WriteLine(verifier.LastErrorText);
    return;
}

int numSigs = verifier.NumSignatures;
int verifyIdx = 0;
while (verifyIdx < numSigs) {
    verifier.Selector = verifyIdx;
    bool verified = verifier.VerifySignature(true);
    if (verified != true) {
        Debug.WriteLine(verifier.LastErrorText);
        return;
    }

    verifyIdx = verifyIdx + 1;
}

Debug.WriteLine("All signatures were successfully verified.");