gnodet-bot commented on code in PR #26726:
URL: https://github.com/apache/camel/pull/26726#discussion_r4069341603


##########
components/camel-xmlsecurity/src/main/java/org/apache/camel/component/xmlsecurity/api/DefaultXmlSignature2Message.java:
##########
@@ -314,6 +350,96 @@ protected Node getNodeForMessageBodyInEnvelopingCase(Input 
input) throws Excepti
         return node;
     }
 
+    /**
+     * Checks that a validated Reference actually covered the document element 
the default search is about to emit.
+     * <p>
+     * Core signature validation only proves that each Reference's digest 
matches the content that Reference resolves
+     * to. It says nothing about the rest of the document. So an attacker can 
take a legitimately signed fragment, embed
+     * it unchanged inside a larger document of their own, and validation 
still passes - the same-document URI resolves
+     * to that fragment exactly as before - while this method would hand the 
whole attacker document downstream as
+     * verified content. That is XML signature wrapping.
+     * <p>
+     * The check is deliberately narrow, so that it rejects that shape and 
nothing else. It only complains when the
+     * signature carries same-document references and none of them covers the 
document element. A Reference with an
+     * empty URI covers the whole document, and a signature whose References 
are all external says nothing about this
+     * document either way, so both are left alone.
+     *
+     * @param input           the verification input, carrying the validated 
References
+     * @param documentElement the element the default search would emit
+     */
+    protected void checkDocumentElementIsCoveredByAReference(Input input, 
Element documentElement) throws Exception {
+        List<Reference> references = getReferencesForMessageMapping(input);
+        if (references == null || references.isEmpty()) {
+            return;
+        }
+
+        boolean sameDocumentReferenceSeen = false;
+        for (Reference reference : references) {
+            String uri = reference.getURI();
+            if (uri == null) {
+                // Nothing to correlate against
+                return;

Review Comment:
   ⚠️ **Wrong comment — `null` URI means whole-document in XML DSig.**
   
   Per JSR-105 (`javax.xml.crypto.dsig.Reference.getURI()`), `null` means the 
URI attribute is **absent** from the `<Reference>` element. Per XML DSig 1.0 
§4.4.3, an absent URI dereferences the same document (the whole octect stream), 
equivalent to `URI=""`. Apache Santuario returns `null` for absent URIs in 
exactly this sense.
   
   So `null` here means _"the whole document is referenced"_ — it IS coverage, 
not the absence of it. The comment "Nothing to correlate against" is factually 
wrong and misleading to anyone maintaining this code. It should say the same 
thing as the `uri.isEmpty()` branch does.
   
   Also: there is no test for the `null`-URI case. Add one:
   
   ```suggestion
               if (uri == null) {
                   // Absent URI (same as URI="") — the whole document is 
covered
                   return;
               }
   ```
   
   And add to the test class:
   ```java
   @Test
   void aNullReferenceUriTreatedAsWholeDocumentCoverage() throws Exception {
       check(WRAPPED, null);
   }
   ```
   where `check` passes `null` to `TestReference` (the `getURI()` stub already 
returns the constructor argument, so this just works).



##########
components/camel-xmlsecurity/src/main/java/org/apache/camel/component/xmlsecurity/api/DefaultXmlSignature2Message.java:
##########
@@ -314,6 +350,96 @@ protected Node getNodeForMessageBodyInEnvelopingCase(Input 
input) throws Excepti
         return node;
     }
 
+    /**
+     * Checks that a validated Reference actually covered the document element 
the default search is about to emit.
+     * <p>
+     * Core signature validation only proves that each Reference's digest 
matches the content that Reference resolves
+     * to. It says nothing about the rest of the document. So an attacker can 
take a legitimately signed fragment, embed
+     * it unchanged inside a larger document of their own, and validation 
still passes - the same-document URI resolves
+     * to that fragment exactly as before - while this method would hand the 
whole attacker document downstream as
+     * verified content. That is XML signature wrapping.
+     * <p>
+     * The check is deliberately narrow, so that it rejects that shape and 
nothing else. It only complains when the
+     * signature carries same-document references and none of them covers the 
document element. A Reference with an
+     * empty URI covers the whole document, and a signature whose References 
are all external says nothing about this
+     * document either way, so both are left alone.
+     *
+     * @param input           the verification input, carrying the validated 
References
+     * @param documentElement the element the default search would emit
+     */
+    protected void checkDocumentElementIsCoveredByAReference(Input input, 
Element documentElement) throws Exception {
+        List<Reference> references = getReferencesForMessageMapping(input);
+        if (references == null || references.isEmpty()) {
+            return;
+        }
+
+        boolean sameDocumentReferenceSeen = false;
+        for (Reference reference : references) {
+            String uri = reference.getURI();
+            if (uri == null) {
+                // Nothing to correlate against
+                return;
+            }
+            if (uri.isEmpty()) {
+                // The whole document is covered
+                return;
+            }
+            if (!uri.startsWith("#")) {
+                // External reference - it tells us nothing about the document 
we are emitting
+                continue;
+            }
+            sameDocumentReferenceSeen = true;
+            String identifier = uri.substring(1);
+            if (identifier.startsWith("xpointer(/)")) {
+                // #xpointer(/) is the whole document
+                return;
+            }
+            if (coversElement(identifier, documentElement)) {
+                return;
+            }
+        }
+
+        if (sameDocumentReferenceSeen) {
+            throw new XmlSignatureException(
+                    "Cannot extract the root node for the output document from 
the XML signature document. "
+                                            + "None of the validated 
References covers the document element, so the "
+                                            + "document contains content which 
was not signed. Configure an output node "
+                                            + "search, or an 
XmlSignatureChecker, which selects the signed content.");
+        }
+    }
+
+    private static boolean coversElement(String identifier, Element 
documentElement) {
+        String xpointerId = getXPointerId(identifier);
+        String id = xpointerId != null ? xpointerId : identifier;
+
+        for (String attribute : ID_ATTRIBUTE_NAMES) {
+            if (id.equals(documentElement.getAttribute(attribute))) {

Review Comment:
   ⚠️ **`xml:id` (XML DSig 1.1 / RFC 3275) not handled — causes false 
rejection.**
   
   `ID_ATTRIBUTE_NAMES` covers `"Id"`, `"ID"`, `"id"` (XML DSig 1.0 convention) 
and the `getElementById()` fallback covers schema-declared ID attributes, but 
there is no handling for `xml:id` (defined in the [XML namespace 
spec](https://www.w3.org/TR/xml-id/)).
   
   XML DSig 1.1 explicitly recommends `xml:id` for element identification. A 
document whose root carries `xml:id="myID"` and whose signature references 
`#myID` would:
   1. validate correctly (the digest matches),
   2. **but throw `XmlSignatureException`** when 
`enforceReferenceCoverage=true` because neither the attribute-name loop nor 
`getElementById()` (which only looks at schema-declared ID types) finds the 
match.
   
   This is a false rejection that silently breaks valid signatures on XML DSig 
1.1 documents. Add `xml:id` to the check:
   
   ```suggestion
           for (String attribute : ID_ATTRIBUTE_NAMES) {
               if (id.equals(documentElement.getAttribute(attribute))) {
                   return true;
               }
           }
           // xml:id (XML DSig 1.1 / W3C xml:id spec)
           if 
(id.equals(documentElement.getAttributeNS("http://www.w3.org/XML/1998/namespace";,
 "id"))) {
               return true;
           }
   ```
   
   Also add a test case:
   ```java
   @Test
   void aXmlIdAttributeIsRecognised() throws Exception {
       check("<signed xml:id=\"myID\"><b>bValue</b></signed>", "#myID");
   }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to