2

An API client I have developed works with XML messages and the messages are signed according to the XML Signature Syntax and Processing specification. After a long struggle, I finally got the signatures working.

At this moment I am building the XML with HEREDOC (simply php strings) and with a cleanup, I'd like to create the XML with DOMDocument directly. However, this causes the message to be invalidated by the server.

This is the current setup (server accepts this message when signed):

$xml = <<<EOT
<?xml version="1.0" encoding="UTF-8"?>
<DirectoryReq xmlns="http://www.idealdesk.com/ideal/messages/mer-acq/3.3.1" version="3.3.1">
  <createDateTimestamp>$timestamp</createDateTimestamp>
  <Merchant>
    <merchantID>$merchantId</merchantID>
    <subID>$subId</subID>
  </Merchant>
</DirectoryReq>
EOT;

$document = new DOMDocument();
$document->loadXML($xml);

This is the OO approach (server rejects this message when signed):

$document = new DOMDocument('1.0', 'UTF-8');
$request  = $document->createElement('DirectoryReq');

$xmlns = $document->createAttribute('xmlns');
$xmlns->value = 'http://www.idealdesk.com/ideal/messages/mer-acq/3.3.1';

$version = $document->createAttribute('version');
$version->value = '3.3.1';

$request->appendChild($xmlns);
$request->appendChild($version);

$merchant = $document->createElement('Merchant');
$merchant->appendChild($document->createElement('merchantID', $merchantId));
$merchant->appendChild($document->createElement('subID', $subId));

$request->appendChild($document->createElement('createDateTimestamp', $timestamp));
$request->appendChild($merchant);

$document->appendChild($request);

What can cause the difference as such the XML signature is invalidated by the server? The code to sign the message is exactly the same. The server is simply reporting "Invalid electronic signature".

If required I can show more code.

EDIT, more output and comparison of XML generated

To give some more information, this is the output of the first (HEREDOC) xml, generated via $document->saveXml():

<?xml version="1.0" encoding="UTF-8"?>
<DirectoryReq xmlns="http://www.idealdesk.com/ideal/messages/mer-acq/3.3.1" version="3.3.1">
  <createDateTimestamp>2013-08-10T19:41:20.000Z</createDateTimestamp>
  <Merchant>
    <merchantID>0020XXXXXX</merchantID>
    <subID>0</subID>
  </Merchant>
</DirectoryReq>

This is the output ($document->saveXML()) for the second (direct DOMDocument generation) method:

<?xml version="1.0" encoding="UTF-8"?>
<DirectoryReq xmlns="http://www.idealdesk.com/ideal/messages/mer-acq/3.3.1" version="3.3.1">
  <createDateTimestamp>2013-08-10T19:41:20.000Z</createDateTimestamp>
  <Merchant>
    <merchantID>0020XXXXXX</merchantID>
    <subID>0</subID>
  </Merchant>
</DirectoryReq>

In php, var_dump() gives the exact same string length. If I compare both strings (=== obviously) they are the same. Comparing both objects, then they are not the same.

Signing example

Signing occurs with the library xmlseclibs with this code (NB. both types are signed the same way!):

public function sign(DOMDocument $document, $fingerprint, $keyfile, $passphrase = null)
{
    $dsig = new XMLSecurityDSig();
    $dsig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
    $dsig->addReference($document, XMLSecurityDSig::SHA256,
        array('http://www.w3.org/2000/09/xmldsig#enveloped-signature'),
        array('force_uri' => true)
    );

    $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type' => 'private'));
    if ($passphrase !== null) {
        $key->passphrase = $passphrase;
    }

    $key->loadKey($keyfile, true);
    $dsig->sign($key);

    $dsig->addKeyInfoAndName($fingerprint);
    $dsig->appendSignature($document->documentElement);
}

If I dump the XML after it's signed, the <DigestValue> and <SignatureValue> values are different. So the server is correct the signature is invalid, but I cannot come up with a clue why method A works and B not.

neubert
  • 15,947
  • 24
  • 120
  • 212
Jurian Sluiman
  • 13,498
  • 3
  • 67
  • 99
  • 1
    [an earlier answersuggest that xmlseclibs XML canonicalization is flawed](http://stackoverflow.com/a/2201304/367456). That might also explain your experience. That answer you give then, sounds just misleading then (at best) then in case there is an error in phpseclibs at that end. – hakre Jul 24 '14 at 08:40

2 Answers2

0

You are overwriting $merchant when you create the Merchant element, so just rename the variable

$merchantElement = $document->createElement('Merchant');
Musa
  • 96,336
  • 17
  • 118
  • 137
  • That was a typo as I got the data from an array and I copy/pasted it into a slightly more readable format. All the values are injected as required, afaik the issue is something else. – Jurian Sluiman Aug 10 '13 at 18:51
  • @JurianSluiman Without **actual** code you wont be able to get much help – Musa Aug 10 '13 at 18:54
  • here are the examples for both the XML output and signature functions. The digest and signature itself are different, resulting in the given error. – Jurian Sluiman Aug 10 '13 at 20:01
0

I have now solved it by exporting and importing the XML again. It's quite ugly, but allows me to flexibly handle the DOMNodes.

protected function repairDOMDocument(DOMDocument $document)
{
    $xml = $document->saveXML();

    $document = new DOMDocument;
    $document->loadXML($xml);

    return $document;
}

If there is a suggestion how to stop doing this, I am pleased to hear so.

Jurian Sluiman
  • 13,498
  • 3
  • 67
  • 99
  • There is no need to re-invent the wheel, the method is called [`DOMDocument::normalizeDocument()`](http://php.net/manual/en/domdocument.normalizedocument.php) and that has nothing to do with "repair" btw ("reload" would have been a better term as that is exactly what you're doing). – hakre Jul 24 '14 at 08:41
  • Reading about the link you post in the comment of my question, I might fix the issue by replacing `$xmlns = $document->createAttribute('xmlns'); $xmlns->value = 'http://www.idealdesk.com/ideal/messages/mer-acq/3.3.1';` by `$document->setAttributeNS('http://www.idealdesk.com/ideal/messages/mer-acq/3.3.1')`. I don't have time to look into it now, but thanks for this answer and I will check it out! If it doesn't work, I will try `normalizeDocument()` as an alternative. – Jurian Sluiman Jul 24 '14 at 09:13