PHP xmllib XSD validation namespace error: Did not expect X, expected X

PHP xmllib XSD validation namespace error: Did not expect X, expected X

我一直在尝试让 xmllib 合作并通过 DOMDocument 验证 xmldsig <Signature> 元素,但无论如何它都会不断产生虚假错误我如何将元素扔向它。显然,由于 DOMDocument,将 <Signature> 设为 root 是不行的。我无法在根上正确设置 xmlns 属性:Trying to validate results in:

Element 'Signature': No matching global declaration available for the validation root. 

我认为这可能是因为架构旨在 'included',毕竟您想要签名 'something',而不仅仅是签名本身。所以,通过创建这个:

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
    version="1.0"
    elementFormDefault="qualified">
<xs:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="xmldsig-core-schema.xsd" />
<xs:element name="root">
    <xs:complexType>
        <xs:sequence>
            <xs:element ref="ds:Signature" />
        </xs:sequence>
    </xs:complexType>
</xs:element>
</xs:schema>

我现在可以包含虚拟元素的签名。

我意识到 PHP XML 处理很糟糕,none 的工具真的很好用。但是,一旦它真正正确地将事情交给 xmllint,这样做应该没有问题。其他答案也建议添加 XPath,但我想保留 KISS;尽可能少地使用 PHP 库。

通过几百行代码的欺骗,我让它合作并将 xml 文件的 <Signature> 部分包装到一个普通的测试文件中,在 <root> 元素中.

我认为它正在破坏文档中的命名空间。失败后调用$myDocument->saveXML(); $myDocument->schemaValidate(); returns 奇怪的结果:

Element 'Signature': This element is not expected. Expected is ( {http://www.w3.org/2000/09/xmldsig#}Signature ).

当 xml 看起来像这样(我添加了格式;真实的东西是未格式化的;虽然空格不应该很重要):

<?xml version="1.0"?>
<root>
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
         (... omitted crypto ...)
    </Signature>
</root>

根据我对它所做的阅读,该错误应该意味着 <Signature> 元素上的命名空间错误。但是这里用 xmlns 清楚地明确指定了错误消息所期望的内容。那么,这里出了什么问题?

附录:准备验证签名的代码(在任意文档中)看起来像这样。这是一个复杂的版本,我在其中添加了一个 'root' 元素而不是直接使用模式,这在下面我的回答中被证明是不必要的,尽管这是一个很好的测试,因为在更复杂的场景中你d 想在一次调用 libxml 中验证一个签名的东西,这是一件有效的事情:

function verifySchema(\DOMDocument $doc) {        
    libxml_use_internal_errors(true);
    $new = new \DOMDocument; 
    $newRoot = $new->createElement("root");
    $root = $doc->documentElement;     
    foreach($root->childNodes as $node) {   
        if($node->nodeName == "Signature") {
            $signature = $new->createElement("Signature");
            $signature->setAttributeNS('http://www.w3.org/2000/xmlns/','xmlns','http://www.w3.org/2000/09/xmldsig#');
            foreach($node->childNodes as $subnode) {
                $signature->appendChild($new->importNode($subnode, true));
            }
            $newRoot->appendChild($signature);
            break;
        }
    }           
    $new->appendChild($newRoot);
    if(!$signature) {
        throw new \Exception("Document is not signed: No signature node found.");
    }
    if(!$new->schemaValidate("xmldsig-test.xsd")) {
       // Error handling code here.
   } else {
       return true;
   }
}

PHP 是问题所在。它有细微的错误,并且不会t/doesn无法正确理解 XML 以编程方式创建的文档中的命名空间。

adding/editing DOMDocument 元素有多种方式,其中 some/most 可以导致某个名称空间 [=38= 中的元素 E ]X 当您只查看 XML 输出时,但 DOMDocument 对象认为它位于命名空间 Y 中。即使您只使用提供的函数而不访问对象的内部结构,也有可能将它们置于不一致的状态。在某些情况下,如果您想要特定 格式的名称空间(由于规范化问题)甚至是不可避免的。

参见:

https://bugs.php.net/bug.php?id=78352

替换代码行

$myDocument->schemaValidate();

$myDocument->loadXML($myDocument->saveXML());
$myDocument->schemaValidate();

并且验证错误应该消失了。无论哪种方式(使用带有 include 的自定义 xsd 或直接使用 xmldsig 验证文件)都应该有效。

虽然错误被视为 'not a bug',但它确实是一个问题,因为 DOMDOcument 实际上不会使用 xmlns 属性输出准确的语法,而是使用前缀符号相反,如果您使用 createElementNS()。 (虽然语法上可能相同,但没有人理解 xml 名称空间,因此接受您的 XML 的人可能希望它与示例完全相同,即:使用 xmlns 而不是 使用 ds:Signature 个元素)。

而不是使用 DOMElement::setAttributeNS($currentNS, 'xmlns', $subElementNS) 输出默认的命名空间标签,但 正确地标记元素下的编程实体使用新命名空间递归。