c++csoaplibxml2xml-signature

How to canonicalize and digest the Body of SOAP with xmlib2


I am trying to sign manually a SOAP request. Using libxml2 in my cpp app I can canonicalize and digest the whole xml soap document using the xmlC14NDocDumpMemory function with xmlNodeSetPtr as null argument. However for the actual signature I need specifically process only the Body of the SOAP.

   xmlChar* canon;

    int canonLength = xmlC14NDocDumpMemory(
        doc,
        nullptr,
        xmlC14NMode::XML_C14N_EXCLUSIVE_1_0,
        nullptr,
        1,
        &canon
    );

std::string canonizedString(reinterpret_cast<const char*>(canon), canonLength);

std::cout << "canonized string:\n" << canonizedString << std::endl;

//freeing the original parsed file:
xmlFreeDoc(doc);

//digesting the value:
const EVP_MD* md = EVP_sha1(); // algorithm

unsigned char digest_value[EVP_MAX_MD_SIZE]; //the digest result value
unsigned int dv_length; //the digest result length


EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); //create msg digest context
EVP_DigestInit_ex(mdctx, md, NULL);  // Initialise the context (engine null value?)
EVP_DigestUpdate(mdctx, canon, canonLength); //digesting
EVP_DigestFinal_ex(mdctx, digest_value, &dv_length); //finalizing
EVP_MD_CTX_free(mdctx); //free the context


char* base64convert;
size_t base64convertLength;

base64::Encode(
    reinterpret_cast<const char*>(digest_value),
    dv_length,
    &base64convert,
    &base64convertLength);

std::string digest64(base64convert, base64convertLength);

std::cout << "\ndigest value:\n" << digest64;

Example SOAP:

<?xml version="1.0" encoding="UTF-8"?>
<e:Envelope xmlns:e="http://schemas.xmlsoap.org/soap/envelope/">
    <e:Header/>
    <e:Body>
        <!-- doctor-E,EGN:XXXXXXXXXX -->
        <ws:hbIn xmlns:ws="http://pis.technologica.com/ws/">
            <ws:egn>XXXXXXXXXX</ws:egn>
            <ws:date>2015-06-11</ws:date>
        </ws:hbIn>
    </e:Body>
</e:Envelope>

The result from the sign-example:

    <e:Envelope xmlns:e="http://schemas.xmlsoap.org/soap/envelope/">
        <e:Header>
        <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
            <SignedInfo>
                <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/>
                <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
                    <Reference URI="#signedContent">
                        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>                          
<DigestValue>93BoBU73Y0Efm1rarqzUGw5x4SU=</DigestValue>
                    </Reference>
            </SignedInfo>
                <SignatureValue>jQGU7xsoSVGx/a......</SignatureValue>
                    <KeyInfo>
                        <X509Data>
                            <X509Certificate>MIICjzCCAfi......</X509Certificate>
                        </X509Data>
                    </KeyInfo>
                </Signature>
            </e:Header>
        <e:Body id="signedContent">
            <!-- doctor-E,EGN:XXXXXXXXXX -->
            <ws:hbIn xmlns:ws="http://pis.technologica.com/ws/">
                <ws:egn>XXXXXXXXXX</ws:egn>
                <ws:date>2015-06-11</ws:date>
            </ws:hbIn>
        </e:Body>
    </e:Envelope>

So the question is how to get the 93BoBU73Y0Efm1rarqzUGw5x4SU= value? I've noticed the signed soap body has URI id "signedContent". I'm not sure if this URI is inserted before or after digestion (it could potentially change the whole hash if it is inserted before).


Solution

  • My advice to anyone, who stumbles here is - DON'T try to canonicalize, digest and sign by yourself. Use xmlsec. Although it's pain in the ass to build (I'm on windows) and the documentation is almost non-existent, it gets the job done. The final answer can be found here: Sign part of XML using xmlsec