Question

I'm trying to implement WS-Security for my PHP SOAP client. The first step is being able to generate a valid XML digest from the outgoing request, but I haven't been able to do this. I've been looking for answers for a few days now but most of the answers end up being something like "don't solve it yourself, just use an existing Java library". That's not feasible in my current situation.

I have been looking at several examples on the net trying to reproduce the same digest they have, for example this one from Microsoft. That page lists the following example:

<ds:Object Id="ts-text">
    Wed Jun  4 12:11:06 EDT
</ds:Object>

Then they show the expected digest value:

<ds:Reference URI="#ts-text">
    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
    <ds:DigestValue>pN3j2OeC0+/kCatpvy1dYfG1g68=</ds:DigestValue>
</ds:Reference>

This is the code I've been using to calculate the digest value:

<?php
$digest = base64_encode(hash('SHA1', $contents, true));

I've tried many different combinations of removing whitespace or using just the timestamp with no XML tags with no success. I've also tried more complex examples where cannonicalization is needed. This is one of my unit tests:

public function testCreateDigest(DOMDocument $request, $expectedDigest) {

    $ns = $request->documentElement->namespaceURI;
    $body = $request
            ->getElementsByTagNameNS($ns, 'Body')
            ->item(0);

    $firstElement = '';
    foreach($body->childNodes as $node){
        if ($node->nodeType === XML_ELEMENT_NODE) {
            $firstElement = $node;
            break;
        }
    }


    $content = $firstElement->C14N(false, true);


    $actualDigest = base64_encode(hash('SHA1', $content, true));

    $this->assertEquals($expectedDigest, $actualDigest);

}

What exactly am I supposed to hash? Am I missing any steps?

Was it helpful?

Solution

I found the solution. For most of the examples I tried, the required transformation was exclusive canonicalization with comments: http://www.w3.org/TR/2002/REC-xml-exc-c14n-20020718/#WithComments

So my problem was that I was doing the transformation wrong. The first parameter in PHP's C14N function defines whether to use exclusive transformation or not. This is what the code should have been (note that I removed the unnecessary traversal to the first element):

public function testCreateDigest(DOMDocument $request, $expectedDigest) {

    $ns = $request->documentElement->namespaceURI;
    $body = $request
        ->getElementsByTagNameNS($ns, 'Body')
        ->item(0);

    $content = $body->C14N(true, true); // <-- exclusive, with comments

    $actualDigest = base64_encode(hash('SHA1', $content, true));

    $this->assertEquals($expectedDigest, $actualDigest);

}

So there you have it. This served as a reminder for me to double check my XML and understand the standards before I go coding aimlessly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top