phpxmldomdocumentxmlseclibs

How to sign a complex XML document?


I am new in this world of XML and electronic signatures. I'm trying to sign an XML document with the xmlseclibs library.

Perhaps someone has come across this methodology when signing much more complex documents. In my XML document I have this label with no value, so it is in this section where I have to sign the document:

<ext:UBLExtension>
  <ext:ExtensionContent/>
</ext:UBLExtension>

and with this code I am signing the generated document:

PHP

  function sign()
  {
    // Load the XML to be signed
    $doc = new DOMDocument();

    // Open XML
    if ($doc->load('file.xml'))
    {
      echo "<p><b>3. XML loaded.</b></p><br/>";
      // Create a new Security object
      $objDSig = new XMLSecurityDSig();

      // Use the c14n exclusive canonicalization
      $objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
      // Sign using SHA-256
      $objDSig->addReference(
          $doc,
          XMLSecurityDSig::SHA256,
          array('http://www.w3.org/2000/09/xmldsig#enveloped-signature')
      );

      // Create a new (private) Security key
      $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type'=>'private'));


      if (!$pfx = file_get_contents('keys/private.pem'))
      {
            echo "Error: The private key could not be opened!\n";
            exit;
        }
      else
      {
        /*
        If key has a passphrase, set it using
        $objKey->passphrase = '<passphrase>';
        */
        // Load the private key
        $prueba = $objKey->loadKey('keys/private.pem', TRUE);
        echo "<p><b>4.Private key has been loaded!</b></p><br/>";
        // Sign the XML file
        $objDSig->sign($objKey);

      }


      if (!$pfx = file_get_contents('keys/public.crt'))
      {
            echo "Error: The public key could not be opened!\n";
            exit;
        }
      else
      {
        $objDSig->add509Cert(file_get_contents('keys/public.crt'));
        echo "<p><b>5. Public key has been loaded.</b></p><br/>";
        // Append the signature to the XML
        $objDSig->appendSignature($doc->documentElement);
        // Save the signed XML
        $doc->save('signed.xml');
      }
    }
    else
    {
      echo "XML could not be opened!";
    }
  }

Result

<?xml version="1.0" encoding="UTF-8"?>
<fe:Invoice xmlns:fe="http://www.dian.gov.co/contratos/facturaelectronica/v1" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns:clm54217="urn:un:unece:uncefact:codelist:specification:54217:2001" xmlns:clm66411="urn:un:unece:uncefact:codelist:specification:66411:2001" xmlns:clmIANAMIMEMediaType="urn:un:unece:uncefact:codelist:specification:IANAMIMEMediaType:2003" xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2" xmlns:qdt="urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2" xmlns:sts="http://www.dian.gov.co/contratos/facturaelectronica/v1/Structure" xmlns:udt="urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.dian.gov.co/contratos/facturaelectronica/v1 ../xsd/DIAN_UBL.xsd urn:un:unece:uncefact:data:specification:UnqualifiedDataTypesSchemaModule:2 ../../ubl2/common/UnqualifiedDataTypeSchemaModule-2.0.xsd urn:oasis:names:specification:ubl:schema:xsd:QualifiedDatatypes-2 ../../ubl2/common/UBL-QualifiedDatatypes-2.0.xsd">
  <ext:UBLExtensions>
    <ext:UBLExtension>
      <ext:ExtensionContent>
        <sts:DianExtensions>
          <sts:InvoiceControl>
            <sts:InvoiceAuthorization>9000000107023546</sts:InvoiceAuthorization>
            <sts:AuthorizationPeriod>
              <cbc:StartDate>2018-04-20</cbc:StartDate>
              <cbc:EndDate>2026-12-21</cbc:EndDate>
            </sts:AuthorizationPeriod>
            <sts:AuthorizedInvoices>
              <sts:Prefix>PRUE</sts:Prefix>
              <sts:From>980000000</sts:From>
              <sts:To>985000000</sts:To>
            </sts:AuthorizedInvoices>
          </sts:InvoiceControl>
          <sts:InvoiceSource>
            <cbc:IdentificationCode listAgencyID="6" listAgencyName="United Nations Economic Commission for Europe" listSchemeURI="urn:oasis:names:specification:ubl:codelist:gc:CountryIdentificationCode-2.0">CO</cbc:IdentificationCode>
          </sts:InvoiceSource>
          <sts:SoftwareProvider>
            <sts:ProviderID schemeAgencyID="195" schemeAgencyName="CO, DIAN (Direccion de Impuestos y Aduanas Nacionales)">900411455</sts:ProviderID>
            <sts:SoftwareID schemeAgencyID="195" schemeAgencyName="CO, DIAN (Direccion de Impuestos y Aduanas Nacionales)">3e82b097-fed0-44e0-8fbc-370fa4bbe718</sts:SoftwareID>
          </sts:SoftwareProvider>
          <sts:SoftwareSecurityCode schemeAgencyID="195" schemeAgencyName="CO, DIAN (Direccion de Impuestos y Aduanas Nacionales)">8f86d8751e4cb9c9c6107bc3d95f727796ad5401e55859cab92c8e28d380f2f4e6bbfe66ded0f2c22344d23661c86da7</sts:SoftwareSecurityCode>
        </sts:DianExtensions>
      </ext:ExtensionContent>
    </ext:UBLExtension>
    <ext:UBLExtension>
      <ext:ExtensionContent/>
    </ext:UBLExtension>
  </ext:UBLExtensions>
  <cbc:UBLVersionID>UBL 2.0</cbc:UBLVersionID>
  <cbc:ProfileID>DIAN 1.0</cbc:ProfileID>
  <cbc:ID>PRUE980000007</cbc:ID>
  <cbc:UUID schemeAgencyID="195" schemeAgencyName="CO, DIAN (Direccion de Impuestos y Aduanas Nacionales)">b1f15bbfbd3743c5a43aa60f6e6fe79467719153</cbc:UUID>
  <cbc:IssueDate>2018-07-06</cbc:IssueDate>
  <cbc:IssueTime>08:04:19</cbc:IssueTime>
  <cbc:InvoiceTypeCode listAgencyID="195" listAgencyName="CO, DIAN (Direccion de Impuestos y Aduanas Nacionales)" listSchemeURI="http://www.dian.gov.co/contratos/facturaelectronica/v1/InvoiceType">1</cbc:InvoiceTypeCode>
  <cbc:Note>FACTURA DE PRUEBA</cbc:Note>
  <cbc:DocumentCurrencyCode>COP</cbc:DocumentCurrencyCode>
  <fe:AccountingSupplierParty>
    <cbc:AdditionalAccountID>1</cbc:AdditionalAccountID>
    <fe:Party>
      <cac:PartyIdentification>
        <cbc:ID schemeAgencyID="195" schemeAgencyName="CO, DIAN (Direccion de Impuestos y Aduanas Nacionales)" schemeID="31">900411455</cbc:ID>
      </cac:PartyIdentification>
      <cac:PartyName>
        <cbc:Name>EMPRESA DE PRUEBA</cbc:Name>
      </cac:PartyName>
      <fe:PhysicalLocation>
        <fe:Address>
          <cbc:Department>BOGOTA, DC</cbc:Department>
          <cbc:CitySubdivisionName>BOGOTA, DC</cbc:CitySubdivisionName>
          <cbc:CityName>BOGOTA, DC</cbc:CityName>
          <cac:AddressLine>
            <cbc:Line>DIRECCION DEL CLIENTE</cbc:Line>
          </cac:AddressLine>
          <cac:Country>
            <cbc:IdentificationCode>CO</cbc:IdentificationCode>
          </cac:Country>
        </fe:Address>
      </fe:PhysicalLocation>
      <fe:PartyTaxScheme>
        <cbc:TaxLevelCode>2</cbc:TaxLevelCode>
        <cac:TaxScheme/>
      </fe:PartyTaxScheme>
      <fe:PartyLegalEntity>
        <cbc:RegistrationName>EMPRESA DE PRUEBA</cbc:RegistrationName>
      </fe:PartyLegalEntity>
    </fe:Party>
  </fe:AccountingSupplierParty>
  <fe:AccountingCustomerParty>
    <cbc:AdditionalAccountID>2</cbc:AdditionalAccountID>
    <fe:Party>
      <cac:PartyIdentification>
        <cbc:ID schemeAgencyID="195" schemeAgencyName="CO, DIAN (Direccion de Impuestos y Aduanas Nacionales)" schemeID="31">830507412</cbc:ID>
      </cac:PartyIdentification>
      <fe:PhysicalLocation>
        <fe:Address>
          <cbc:Department>BOGOTA DC</cbc:Department>
          <cbc:CitySubdivisionName>BOGOTA</cbc:CitySubdivisionName>
          <cbc:CityName>CITY</cbc:CityName>
          <cac:AddressLine>
            <cbc:Line>DIRECCION</cbc:Line>
          </cac:AddressLine>
          <cac:Country>
            <cbc:IdentificationCode>CO</cbc:IdentificationCode>
          </cac:Country>
        </fe:Address>
      </fe:PhysicalLocation>
      <fe:PartyTaxScheme>
        <cbc:TaxLevelCode>2</cbc:TaxLevelCode>
        <cac:TaxScheme/>
      </fe:PartyTaxScheme>
      <fe:Person>
        <cbc:FirstName>NOMBRE DE EMPRESA</cbc:FirstName>
        <cbc:FamilyName>APELLIDO</cbc:FamilyName>
        <cbc:MiddleName>NOMBRE</cbc:MiddleName>
      </fe:Person>
    </fe:Party>
  </fe:AccountingCustomerParty>
  <fe:TaxTotal>
    <cbc:TaxAmount currencyID="COP">0.00</cbc:TaxAmount>
    <cbc:TaxEvidenceIndicator>false</cbc:TaxEvidenceIndicator>
    <fe:TaxSubtotal>
      <cbc:TaxableAmount currencyID="COP">0.00</cbc:TaxableAmount>
      <cbc:TaxAmount currencyID="COP">0.00</cbc:TaxAmount>
      <cbc:Percent>19.00</cbc:Percent>
      <cac:TaxCategory>
        <cac:TaxScheme>
          <cbc:ID>01</cbc:ID>
        </cac:TaxScheme>
      </cac:TaxCategory>
    </fe:TaxSubtotal>
  </fe:TaxTotal>
  <fe:LegalMonetaryTotal>
    <cbc:LineExtensionAmount currencyID="COP">0.00</cbc:LineExtensionAmount>
    <cbc:TaxExclusiveAmount currencyID="COP">0.00</cbc:TaxExclusiveAmount>
    <cbc:PayableAmount currencyID="COP">0.00</cbc:PayableAmount>
  </fe:LegalMonetaryTotal>
  <fe:InvoiceLine>
    <cbc:ID>PV00000001</cbc:ID>
    <cbc:InvoicedQuantity>1.00</cbc:InvoicedQuantity>
    <cbc:LineExtensionAmount currencyID="COP">20.00</cbc:LineExtensionAmount>
    <fe:Item>
      <cbc:Description>PRODUCTOS VARIOS</cbc:Description>
    </fe:Item>
    <fe:Price>
      <cbc:PriceAmount currencyID="COP">20.00</cbc:PriceAmount>
    </fe:Price>
  </fe:InvoiceLine>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <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"/>
    <ds:Reference>
      <ds:Transforms>
        <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
      </ds:Transforms>
      <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
      <ds:DigestValue>54iNdi7I+p1PnU0zXuXn89HUdlmkMK3ZoRPHpcaqlOQ=</ds:DigestValue>
    </ds:Reference>
  </ds:SignedInfo>
  <ds:SignatureValue>
    WmZG4QHfKqBsjtoFjP3BNTLoZVV3+Bww/XjH4gXW+6FDm+J/fkGR1wnmt9C3t98nwFlpOAl5gaOyhC0DNHce3MYWobMNc08hcFES9EV13/tqNsXiCrXXwXVzmwY7CDge0wGa9jYVo6CaLj+t9+IbvgNVc6HdRuVeLzojmO+iyBOPTC5w5QWVVdRV4HeBsxYNsIRNIclp34TsljHYy1g7sMjMAm0SMHkduwgXNn96q+GX/v8pjckdxeZwAYKB+yW3US6mr5lzvyWr9e7pQ0LqZBgX3tT9b050rQHBE8t/H4sh665JyBjSafBSV8/NYRgEafhqBvL0SLVwOESvmvg98Q==
  </ds:SignatureValue>
  <ds:KeyInfo>
    <ds:X509Data>
      <ds:X509Certificate>
        MIID0DCCArigAwIBAgIJAKRguh9Kxjd4MA0GCSqGSIb3DQEBCwUAMH0xCzAJBgNVBAYTAkJSMQswCQYDVQQIDAJTUDEMMAoGA1UEBwwDUmlvMQ4wDAYDVQQKDAVGSVJNUzEQMA4GA1UECwwHRkVSTUlOTzEQMA4GA1UEAwwHRkVSTUlOTzEfMB0GCSqGSIb3DQEJARYQZ3VpaGdmQGdtYWlsLmNvbTAeFw0xNzA2MjcxMzU2MTBaFw0yNzA2MjUxMzU2MTBaMH0xCzAJBgNVBAYTAkJSMQswCQYDVQQIDAJTUDEMMAoGA1UEBwwDUmlvMQ4wDAYDVQQKDAVGSVJNUzEQMA4GA1UECwwHRkVSTUlOTzEQMA4GA1UEAwwHRkVSTUlOTzEfMB0GCSqGSIb3DQEJARYQZ3VpaGdmQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMxoXF67vmncyWwoJ3h3MK/foNuP7uvmSv1aiLgLgc0dH1SGQWqM33ocloBtHWs1QFpU7TGVVmyS9bf47fdVxa/75MLV7mSMdWDiK0cIBDwQFAhSu+ipQ3kpTJSmJWiXKd16Xtrxq9XtwdwC25kVMEws7ggZoMHzrmmKPtTUwvbaqy0xXw5IFFt/r9L6dzOQcfwdNhQACLlO7cEGdJQXbwvICn3VN6M5nwTXec6vlalppX3kmjTiVJURKi64hmih84Hf12loDbtyiuxYcc8rK0ceUAxhFtmekwDAcV+EAr7sXUrNDKZErpK6HZ/QDrhZVeXzVuKsHyRmYYn0RUGzNqMCAwEAAaNTMFEwHQYDVR0OBBYEFONdUpNRKv8PN9499PBw0zVD+025MB8GA1UdIwQYMBaAFONdUpNRKv8PN9499PBw0zVD+025MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAK5OwB/iVWDHEtGKkQCmMqXOcsVz7PvYJIaGtSUq1X6CWDGZoEtyCkfoMC/FYPz6V8JgSwDFENyw4VaH0HWggwz2vCDEA5Mg3iOobRG98gk9lLolk7DjXsfvHkCYI6ncvz7eIdawwzddhSaj+2LSUCB8UNONYsl6CPdioNGUVJix9QDSrf5gcMkWsWa8Sg3f6vjVCWdgl5mOnJeeXapxvg1VEjnmOLDb25hNKO44i4jVNKN4+jSKCcECCKw7fu7uD4PyVbTCZi037svPzMZ6dG7wlWLBWOopbj7G2sAMMVwCoHAJBoEV8lArRq4n1ZW3DAW9NQ8+OQcJMXWGWQhSmJk=
      </ds:X509Certificate>
    </ds:X509Data>
  </ds:KeyInfo>
</ds:Signature>
</fe:Invoice>

As you can see, the signature appears at the end of the document, but what I need is to generate the signature within the aforementioned label. Has anyone faced this inconvenience?


Solution

  • Well your code says:

    $objDSig->appendSignature($doc->documentElement);
    

    It seems fairly obvious that this will append the signature to the document element. If you'd like to append it to something else, I'd suggest specifying that as an argument instead.

    One way to do this might be an XPath query to look for an empty element of the type you want to append to:

    $xpath = new DomXPath($doc);
    $nodes = $xpath->query("//ext:ExtensionContent[not(normalize-space())]");
    if ($nodes->count()) {
        $objDSig->appendSignature($nodes[0]);
    }