Menu

#1113 Problem with XML canonicalization and XML namespace defined on toplevel element

v1.0 (example)
closed-works-for-me
None
3
2017-04-04
2017-03-28
No

Hello!

I ran across a problem while verifying the signature of an SAML2 Assertion:
The Assertion is encrypted in the body of a message and after the successfull decryption i have to check its signature.

I use soap_wsse_verify_with_signature(), but it always signals an invalid digest.
As a result it is not possible to successful check the digest of such message.

Further investigations revealed that the incoming document defines a namespace on its toplevel element which is used later in a child node deeper in the tree.

Simplyfied example:

<s:a xmlns:s="urn:acme" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <s:b xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">x</s:b>
</s:a>

The relevant namespace is xmlns:xs="http://www.w3.org/2001/XMLSchema".

During canonicalization, gSoap seems to shift the namespace declaration down to the node where the declaration is required:

<s:a xmlns:s="urn:acme">
  <s:b xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">x</s:b>
</s:a>

This seems to be invalid as RFC3076 Canonical XML Version 1.0 - Chapter 4.6 Superfluous Namespace Declarations - states that

"... The root document element is handled specially since it has no parent
element. All namespace declarations in it are retained, except the
declaration of an empty default namespace is automatically omitted. ..."

The following program shows the effect.

Best regards
Soenke Schau

#include "stdafx.h"
#include "wst.nsmap"
#include "stdsoap2.cpp"
#include "dom.cpp"
#include <sstream>
int main()
{
    std::string Content =
R"(
<s:a xmlns:s="urn:acme" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<s:b xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">x</s:b>
</s:a>
)";
    {
        soap Context(SOAP_IO_DEFAULT | SOAP_DOM_TREE);
        std::stringstream ContentStream;
        ContentStream << Content;
        Context.is = &ContentStream;
        auto SamlAssertion = soap_new_xsd__anyType( &Context );
        if( soap_begin_recv( &Context ) ||
            soap_in_xsd__anyType( &Context, NULL, SamlAssertion, NULL ) == NULL ||
            soap_end_recv( &Context )
            )
        {
            std::cout << "Problem reading content\n";
            return 12;
        }
        std::cout << "XML read - SOAP_DOM_TREE - content:\n";
        std::cout << *SamlAssertion << std::endl;
    }
    {
        soap Context(SOAP_IO_DEFAULT | SOAP_XML_CANONICAL | SOAP_DOM_ASIS );
        std::stringstream ContentStream;
        ContentStream << Content;
        Context.is = &ContentStream;
        auto SamlAssertion = soap_new_xsd__anyType( &Context );
        if( soap_begin_recv( &Context ) ||
            soap_in_xsd__anyType( &Context, NULL, SamlAssertion, NULL ) == NULL ||
            soap_end_recv( &Context )
            )
        {
            std::cout << "Problem reading content\n";
            return 12;
        }
        std::cout << "XML read - SOAP_XML_CANONICAL | SOAP_DOM_ASIS - content:\n";
        std::cout << *SamlAssertion << std::endl;
    }
    return 0;
}

Discussion

  • Sönke Schau

    Sönke Schau - 2017-03-28

    Sorry, forgot to write that i'm using gSoap 2.8.42

     
  • Robert van Engelen

    The problem is not the RFC conflict but the inclusive/exclusive namespaces modes. Namespaces are required to be part of the signed XML, otherwise namespace bindings can be forged and become insecure. Why not use the code that is already available in the WSSE plugin? See the documentation here:

    https://www.genivia.com/doc/wsse/html/wsse.html#wsse_6_4

     
  • Robert van Engelen

    • status: open --> closed-works-for-me
    • assigned_to: Robert van Engelen
     
  • Sönke Schau

    Sönke Schau - 2017-04-03

    Hello!
    I should have written that i already use the available code in the WSSE plugin, and that my example from the original report is just a simplification for this case.

    Here the excerpt:

    std::stringstream DecodedDataStream;
    DecodedDataStream << DecodedData;
    
    soap SoapContext( SOAP_C_UTFSTRING | SOAP_ENC_PLAIN | SOAP_XML_CANONICAL | SOAP_XML_DOM );
    
    SoapContext.is = &DecodedDataStream;
    auto SamlAssertion = soap_new_saml2__AssertionType( &SoapContext );
    
    if( soap_begin_recv( &SoapContext ) ||
        soap_in_saml2__AssertionType( &SoapContext, NULL, SamlAssertion, NULL ) == NULL ||
        soap_end_recv( &SoapContext )
        )
    {
        // handle error ...
    }
    
    soap_register_plugin( &SoapContext, soap_wsse );
    
    // Use callback handler for certificate
    spSaml2AssertionContext->user = PublicKeyFromPartner;
    soap_wsse_set_security_token_handler( &SoapContext,
                                          []( soap *soap, int *alg, const char *keyname, const unsigned char *keyid, int keyidlen, int *keylen ) -> const void*
    {
         return soap->user;
    } );
    
    if( SOAP_OK != soap_wsse_verify_with_signature( &SoapContext, pSaml2Assertion->ds__Signature ) )
    {
         // bad -> handle error ...
    }
    

    This always fails in the soap_wsse_verify_SignedInfo() function with the following data (some data changed):

    <saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_464dee95-5737-4cb4-971a-f74dc5b0cd01" IssueInstant="2017-04-03T06:26:06.344Z" Version="2.0">
      <saml2:Issuer>http://www.acme-provider.com</saml2:Issuer>
      <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
          <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod>
          <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>
          <ds:Reference URI="#_464dee95-5737-4cb4-971a-f74dc5b0cd01">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="xs"></ec:InclusiveNamespaces>
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
            <ds:DigestValue>zEXU65mXaqaHgxy1c7+XVSXf+TE=</ds:DigestValue>
          </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>
          QivQhrQIOVpmSZ4Z+xYOhomssfjCAXiRyHE+MkUcEwrFkWOPvKbjANp1yKiTEsQnD5I5GQQWqZ0O
          VCy0+SqIiLXkDWwVx1gwTDs9hrFxgw9BzMjaJSYHWwfR1z8qU2MaPhyJQqpW8e3bF4kBnpWWB4wB
          zPXjNisNWYWefO7GAV50QLqlJ3CMyJnaQUyNpxlYNtxTMFSzz61hIMSGG7reARkOBOLb61oVhixt
          4SLMxUsTZbq35DB+kUWfoo35hX5i2Aa0mVLhMpvBcrVJbN3ljMxP/AdgH0ISZuDk8i6K/Ml+QykG
          qcUgpYz1W24gSrS05Un4Vxx2mqy9bvY3vvWDjA==
        </ds:SignatureValue>
      </ds:Signature>
      <saml2:Subject>
        <saml2:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">1489750278-3ddad49e-eb49-437d-83c3-cb7404bdfb59</saml2:NameID>
        <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
          <saml2:SubjectConfirmationData NotOnOrAfter="2017-04-03T06:41:06.344Z" Recipient="https://www.acme-target.com/Landingpage"></saml2:SubjectConfirmationData>
        </saml2:SubjectConfirmation>
      </saml2:Subject>
      <saml2:Conditions NotBefore="2017-04-03T06:11:06.344Z" NotOnOrAfter="2017-04-03T06:41:06.344Z">
        <saml2:AudienceRestriction>
          <saml2:Audience>https://www.acme-target.com</saml2:Audience>
        </saml2:AudienceRestriction>
      </saml2:Conditions>
      <saml2:AuthnStatement AuthnInstant="2017-04-03T06:20:33.641Z">
        <saml2:SubjectLocality Address="1.2.3.4"></saml2:SubjectLocality>
        <saml2:AuthnContext>
          <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContract</saml2:AuthnContextClassRef>
        </saml2:AuthnContext>
      </saml2:AuthnStatement>
      <saml2:AttributeStatement>
        <saml2:Attribute Name="AuthMethod">
          <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">secovid</saml2:AttributeValue>
        </saml2:Attribute>
      </saml2:AttributeStatement>
    </saml2:Assertion>
    

    While debugging i found out, that the xs Namespace declaration at the root element is shifted to the "saml2:AttributeValue" Element (where the namespace is first used) when gSoap performs its own serialization in order to check the Message Digest.

    Best regards
    Sönke Schau

     
  • Robert van Engelen

    Problem solved! The 2.8.45 release will be available soon (ETA in a few days). The problem was a flag that was reset too early, leading to the PrefixList (with inclusive C14N prefixes) being ignored, resulting in the prefix binding being removed from the root. Added unit+regression tests for our QA (private repo).

     
  • Sönke Schau

    Sönke Schau - 2017-04-04

    Thank you very much!
    Great project!
    Great support!
    Best regards!
    Sönke

     

Log in to post a comment.

MongoDB Logo MongoDB