针对丹麦卫生数据局的XML摘要值计算异常问题咨询
I'm currently stuck on a frustrating issue while generating an XML document (specifically a SOAP envelope with SAML assertions and WS-Security) that requires a digest value. The digest is supposed to be the Base64-encoded SHA1 hash of a portion of the XML after C14N canonicalization.
Here's the breakdown of what's happening:
- Following the (incorrectly documented) specifications, neither my calculated digest nor the resulting XML are accepted by the receiving system (Danish Health Data Authority).
- I can only get the setup to work with a hacky two-step process that makes no logical sense to me, and I'm hoping someone has encountered this before (especially folks who've worked with the Danish Health Data Authority).
The Bizarre Working Workflow
- First Pass: Generate an XML that includes an extra namespace attribute on the
saml:Assertionelement (as per the flawed documentation, which the final XML should NOT have). Calculate the digest from this element's C14N output and store it. - Second Pass: Generate the final, "correct" XML (without that extra namespace on
saml:Assertion), then insert the digest value calculated in the first pass.
When I generate XML matching the existing system's output directly, my digest doesn't match the existing system's. But when I follow this two-step hack, the digest is correct and the XML is accepted.
My Helper Functions
function createElement(\DOMDocument $xml, \DOMNode /*DOMDocument&DOMElement*/ $parent, string $name) : \DOMElement { $node = $xml->createElement($name) or die("<span class=\"fail\">createElement($name) failed</span>"); return $parent->appendChild($node); } function createElementNS(\DOMDocument $xml, \DOMNode /*DOMDocument&DOMElement*/ $parent, string $ns, string $name) : \DOMElement { $node = $xml->createElementNS($ns, $name) or die("<span class=\"fail\">createElementNS(..., $ns, $name) failed</span>"); return $parent->appendChild($node); } function createAttribute(\DOMDocument $xml, \DOMElement $parent, string $name, string $value) : \DOMAttr { $attribute = $xml->createAttribute($name) or die("<span class=\"fail\">createAttribute(..., $name, $value) failed</span>"); $attribute->value = $value; return $parent->appendChild($attribute); } function createAttributeNS(\DOMDocument $xml, \DOMElement $parent, string $name, string $value) : \DOMAttr { $attribute = $xml->createAttribute($name) or die("<span class=\"fail\">createAttribute{NS}(..., $name, $value) failed</span>"); $attribute->value = $value; return $parent->appendChild($attribute); } function createTextNode(\DOMDocument $xml, \DOMElement $parent, string $text) : \DOMText { $text_node = $xml->createTextNode($text) or die("<span class=\"fail\">createTextNode($text) failed</span>"); return $parent->appendChild($text_node); }
Relevant Code Snippet
$hack_pass = 0; while (++$hack_pass <= 2) { $xml = new \DOMDocument('1.0', 'UTF-8'); $xml_soapenv_envelope = createElementNS($xml, $xml, 'http://schemas.xmlsoap.org/soap/envelope/', 'soapenv:Envelope'); createAttributeNS($xml, $xml_soapenv_envelope, 'xmlns:ds', 'http://www.w3.org/2000/09/xmldsig#'); // ... (other elements omitted for brevity) createAttribute($xml, $xml_soapenv_envelope, 'id', 'Envelope'); $xml_soapenv_header = createElement($xml, $xml_soapenv_envelope, 'soapenv:Header'); $xml_wsse_security = createElement($xml, $xml_soapenv_header, 'wsse:Security'); $xml_wsu_timestamp = createElement($xml, $xml_wsse_security, 'wsu:Timestamp'); $xml_wsu_created = createElement($xml, $xml_wsu_timestamp, 'wsu:Created'); createTextNode($xml, $xml_wsu_created, $created); if ($hack_pass == 1) { // First pass: use namespace (incorrect for final XML) to generate valid digest $xml_saml_assertion = createElementNS($xml, $xml_wsse_security, 'urn:oasis:names:tc:SAML:2.0:assertion', 'saml:Assertion'); } else { // Second pass: no namespace (correct final XML) $xml_saml_assertion = createElement($xml, $xml_wsse_security, 'saml:Assertion'); // Final XML } // ... (populate assertion content, same in both passes) if ($hack_pass < 2) { // Calculate digest from the first pass's assertion $c14n = $xml_saml_assertion->C14N(TRUE); $DigestValue = base64_encode(hash('sha1', $c14n, TRUE)); } // ... (insert DigestValue into the Signature element in the second pass) }
Has anyone dealt with a similar issue where the digest calculation depends on an "incorrect" version of the XML element? Or specifically worked with integrations for the Danish Health Data Authority that required this kind of workaround? I'd love to understand why this hack works and if there's a cleaner way to implement it.
内容的提问来源于stack exchange,提问作者Andrew Rump

