DI Management Home > Cryptography > Signing an XML document using XMLDSIG (Part 2)

Signing an XML document using XMLDSIG (Part 2)


On this page we look at how to create an enveloped signature of an XML document using XML-DSIG. This is part 2 of a series. In Part 1 we showed how to create an enveloping signature.

Contents

Two types of XML-DSIG signatures | Example of enveloped signature | Canonicalize the document and compute the DigestValue | Compose the canonicalized SignedInfo element and compute its SignatureValue | RSA Keys | Compose the final output XML | Testing | Comments on the final XML document | Downloads | Another example (KITH Norway) | Definitions | References | Contact
XML-C14N2017-06-28: See Canonicalization of an XML document for a more detailed how-to guide for canonicalization (C14N) of an XML document prior to signing, and
New2017-07-11: SC14N, a straightforward XML canonicalization utility.
New C# code2017-03-20: Added some code in C# to compute the digest value, signature value, and extract the RSAKeyValue. We also show how to extract the key and certificate files from a PFX file. See below.
2015-06-08: Regardless of what we may have said here and elsewhere, we now recommend that you create XML documents to be signed with XML-DSIG without any whitespace at all between elements. That is, make your XML document one long line without any newlines at all. For example,
<soap:Envelope><soap:Header>...etc...</soap:Header><soap:Body>...etc...</soap:Body></soap:Envelope>
Your users can always use an XML pretty-print utility to view it. Just remember to use the original one-liner when verifying the signature.
2012-10-01: See How to create a SAT Cancelacion document an enveloped XML-DSIG document with the namespace http://cancelacfd.sat.gob.mx issued by the Servicio de Administración Tributaria (SAT) in Mexico.

Feedback

Your article on XML-Dsig was really invaluable by spelling out the steps to take and the exact intermediate results to expect! Any plans to do the same for XML-Enc?
-- Jan
See Creating output for secure XML documents: CipherValue element.

Two types of XML-DSIG signatures

An enveloping signature* is a self-contained Signature document which contains a fragment (URI="#object").

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
  <SignedInfo>
    ...
    <Reference URI="#object">
      <DigestValue>dddd</DigestValue>
    </Reference>
  </SignedInfo>
  <SignatureValue>ssssss</SignatureValue>
  ...
  <Object Id="object">some text
  with spaces and CR-LF.</Object>
</Signature>

We compute the digest value of the canonicalized Object fragment, insert this DigestValue into the SignedInfo element and then compute the signature of the SignedInfo element. Note that the SignedInfo contains a reference to the data that has been signed (URI="#object"). The ID does not have to be called "object", but it must match. For example, we could have URI="#1234" and <Object Id="1234">.

We show how to create the XML signature for this example in Part 1 of this series.

An enveloped signature* is a complete XML document which contains a Signature element.

<Envelope>
  <Body>blah</Body>
  <Signature>
    <SignedInfo>
      ...
      <Reference URI="">
        <DigestValue>dddd</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>ssssss</SignatureValue>
    ...
  </Signature>
</Envelope>

This time we compute the digest value over the complete XML document excluding the entire Signature element (be careful about whitespace here), and then compute the signature of the SignedInfo element containg this DigestValue. Note the empty reference URI="". The root element does not have to be called "Envelope", nor does it need a child element called "Body". It can be any XML structure that includes a Signature element inside it.

This page looks at how to create an enveloped signature.

Example of enveloped signature

The base XML document we want to sign is as follows:

<?xml version="1.0" encoding="ISO-8859-1"?>
<Envelope xmlns="http://example.org/envelope">
  <Body>
    Olá mundo
  </Body>
</Envelope>

In the final version we will need a completed Signature element. Note there are 3 parameters to be completed.

<?xml version="1.0" encoding="ISO-8859-1"?>
<Envelope xmlns="http://example.org/envelope">
  <Body>
    Olá mundo
  </Body>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>????</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>????</SignatureValue>
    <KeyInfo>
      <KeyValue>
        <RSAKeyValue>????</RSAKeyValue>
      </KeyValue>
    </KeyInfo>
  </Signature>
</Envelope>

We will use the sha1WithRSAEncryption signature algorithm (rsa-sha1), which uses the SHA-1 message digest algorithm and RSA PKCS#1v1.5 to create the signature; and also SHA-1 to compute the digest value. Canonicalization will be according to Canonical XML Version 1.0, so white space is important and namespaces are propagated down from parent elements.

We've constructed this example with a few gotchas:

Canonicalize the document and compute the DigestValue

To canonicalize in this example, we do the following

New2017-06-28: See Canonicalization of an XML document for a more detailed how-to guide for canonicalization (C14N) of an XML document prior to signing.

The canonicalized version is as follows, showing a newline character (0x0A) as ¶ and a space (0x20) as ♦:

<Envelope xmlns="http://example.org/envelope">¶
♦♦<Body>¶
♦♦♦♦Olá mundo¶
♦♦</Body>¶
♦♦¶
</Envelope>
000000  3c 45 6e 76 65 6c 6f 70 65 20 78 6d 6c 6e 73 3d  <Envelope xmlns=
000010  22 68 74 74 70 3a 2f 2f 65 78 61 6d 70 6c 65 2e  "http://example.
000020  6f 72 67 2f 65 6e 76 65 6c 6f 70 65 22 3e 0a 20  org/envelope">.
000030  20 3c 42 6f 64 79 3e 0a 20 20 20 20 4f 6c c3 a1   <Body>.    Ol..
000040  20 6d 75 6e 64 6f 0a 20 20 3c 2f 42 6f 64 79 3e   mundo.  </Body>
000050  0a 20 20 0a 3c 2f 45 6e 76 65 6c 6f 70 65 3e     .  .</Envelope>

Note that the two spaces before the Signature element have been retained, as has the newline after it; and that the á character has been encoded in UTF-8 as the two bytes (C3 A1).

The SHA-1 digest of this data is (0x)516b984d8ba0d7427593984a7e89f1b6182b011f or UWuYTYug10J1k5hKfonxthgrAR8= in base64.

Compose the canonicalized SignedInfo element and compute its SignatureValue

The canonicalized version of the SignedInfo element is as follows, with newlines shown as ¶ and leading spaces shown as ♦:

<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">¶
♦♦♦♦♦♦<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>¶
♦♦♦♦♦♦<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>¶
♦♦♦♦♦♦<Reference URI="">¶
♦♦♦♦♦♦♦♦<Transforms>¶
♦♦♦♦♦♦♦♦♦♦<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></Transform>¶
♦♦♦♦♦♦♦♦</Transforms>¶
♦♦♦♦♦♦♦♦<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>¶
♦♦♦♦♦♦♦♦<DigestValue>UWuYTYug10J1k5hKfonxthgrAR8=</DigestValue>¶
♦♦♦♦♦♦</Reference>¶
♦♦♦♦</SignedInfo>

These 626 bytes are shown in this hexdump. Note that:

The SHA-1 digest of this is (0x)a25a06d339d68b625cd7383a932357889956a54e or oloG0znWi2Jc1zg6kyNXiJlWpU4= in base64, and the SignatureValue computed using Alice's RSA key is

TSQUoVrQ0kg1eiltNwIhKPrIdsi1VhWjYNJlXvfQqW2EKk3X37X862SCfrz7v8IYJ7OorWwlFpGDStJDSR6saO
ScqSvmesCrGEEq+U6zegR9nH0lvcGZ8Rvc/y7U9kZrE4fHqEiLyfpmzJyPmWUT9Uta14nPJYsl3cmdThHB8Bs=

Update 2017-08-13: See some code to compute this signature value.

RSA Keys

We used Alice's RSA key to sign this, with PKCS#8 encrypted private key (password: "password"), and corresponding X.509 certificate.

Alice's public key in XML format is

<RSAKeyValue>
  <Modulus>
    4IlzOY3Y9fXoh3Y5f06wBbtTg94Pt6vcfcd1KQ0FLm0S36aGJtTSb6pYKfyX7PqCUQ8wgL6xUJ5GRPEsu9gyz8
    ZobwfZsGCsvu40CWoT9fcFBZPfXro1Vtlh/xl/yYHm+Gzqh0Bw76xtLHSfLfpVOrmZdwKmSFKMTvNXOFd0V18=
  </Modulus>
  <Exponent>AQAB</Exponent>
</RSAKeyValue>

Compose the final output XML

We can now fill in the missing parameters to create our final signed XML document

<?xml version="1.0" encoding="ISO-8859-1"?>
<Envelope xmlns="http://example.org/envelope">
  <Body>
    Olá mundo
  </Body>
  <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>UWuYTYug10J1k5hKfonxthgrAR8=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>
      TSQUoVrQ0kg1eiltNwIhKPrIdsi1VhWjYNJlXvfQqW2EKk3X37X862SCfrz7v8IYJ7OorWwlFpGDStJDSR6saO
      ScqSvmesCrGEEq+U6zegR9nH0lvcGZ8Rvc/y7U9kZrE4fHqEiLyfpmzJyPmWUT9Uta14nPJYsl3cmdThHB8Bs=
    </SignatureValue>
    <KeyInfo>
      <KeyValue>
         <RSAKeyValue>
           <Modulus>
             4IlzOY3Y9fXoh3Y5f06wBbtTg94Pt6vcfcd1KQ0FLm0S36aGJtTSb6pYKfyX7PqCUQ8wgL6xUJ5GRPEsu9gyz8
             ZobwfZsGCsvu40CWoT9fcFBZPfXro1Vtlh/xl/yYHm+Gzqh0Bw76xtLHSfLfpVOrmZdwKmSFKMTvNXOFd0V18=
           </Modulus>
           <Exponent>AQAB</Exponent>
         </RSAKeyValue>
      </KeyValue>
    </KeyInfo>
  </Signature>
</Envelope>

Testing

Test with the XML Security Library Online XML Digital Signature Verifer. You should be able to cut-and-paste the above XML document and it should show "RESULT: Signature is OK".

Comments on the final XML document

Where the whitespace does matter

The whitespace characters shown as ♦ and line endings below cannot be changed because they were required in the c14n forms to compute the digest value or signature value. (You may ask why this should make a difference when the obvious meaning is not changed; but, sorry, get with the program!)

<Envelope xmlns="http://example.org/envelope">
♦♦<Body>
♦♦♦♦Olá mundo
♦♦</Body>
♦♦<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <SignedInfo>
♦♦♦♦♦♦<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
♦♦♦♦♦♦<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
♦♦♦♦♦♦<Reference URI="">
♦♦♦♦♦♦♦♦<Transforms>
♦♦♦♦♦♦♦♦♦♦<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
♦♦♦♦♦♦♦♦</Transforms>
♦♦♦♦♦♦♦♦<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
♦♦♦♦♦♦♦♦<DigestValue>UWuYTYug10J1k5hKfonxthgrAR8=</DigestValue>
♦♦♦♦♦♦</Reference>
♦♦♦♦</SignedInfo>
...
  </Signature>
</Envelope>

Recommendation for whitespace between elements

The example above used whitespace to demonstrate the absurdities of what you need to keep when c14n'ing. In practice, we recommend that you create XML documents to be signed with XML-DSIG without any whitespace at all between elements. That is, make your XML document one long line without any newlines at all. For example,
<soap:Envelope><soap:Header>...etc...</soap:Header><soap:Body>...etc...</soap:Body></soap:Envelope>
Your users can always use an XML pretty-print utility to view it. Just remember to use the original one-liner when verifying the signature.

Downloads

Some VB6 code to create the signature, Alice's encrypted private key private key (password: "password"), the final XML file, and all the above and intermediate files in a zip file (6 kB).

New2017-03-20: Added some C# Code which should give this output. Required files in a zip file (6.5 kB). For hints on using the code in a C# project, see Creating a C# command-line app with CryptoSys PKI.

Another example

This example is a cut-down version of the MsgHead XML used by KITH - Norwegian Centre for Informatics in Health and Social Care.

The base XML to be signed using an XML-DSIG enveloped signature is

<?xml version="1.0" encoding="ISO-8859-1"?>
<MsgHead xmlns="http://www.example.com/msghead" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema.xsd" 
    xsi:schemaLocation="http://www.example.com/msghead MsgHead.xsd">
  <MsgInfo>
    <Type DN="Resept" V="ERM1"></Type>
    <Patient>
      <FamilyName>Nordmann</FamilyName>
      <GivenName>Ola</GivenName>
      <Ident>
        <Id>12345678901</Id>
        <TypeId DN="Fødselsnummer" S="2.16.123.1.10.4.1.1.9999" V="FNR"></TypeId>
      </Ident>
    </Patient>
  </MsgInfo>
  <Document>
    <RefDoc>foo bar</RefDoc>
  </Document>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
????
</Signature>
</MsgHead>

The c14n data to be digested is

<MsgHead xmlns="http://www.example.com/msghead" xmlns:xsd="http://www.w3.org/2001/XMLSchema.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.example.com/msghead MsgHead.xsd">
  <MsgInfo>
    <Type DN="Resept" V="ERM1"></Type>
    <Patient>
      <FamilyName>Nordmann</FamilyName>
      <GivenName>Ola</GivenName>
      <Ident>
        <Id>12345678901</Id>
        <TypeId DN="Fødselsnummer" S="2.16.123.1.10.4.1.1.9999" V="FNR"></TypeId>
      </Ident>
    </Patient>
  </MsgInfo>
  <Document>
    <RefDoc>foo bar</RefDoc>
  </Document>

</MsgHead>

These 590 bytes are shown in this hexdump. Note that the attributes of the root MsgHead element have been put into one line with a single space inbetween each, and the "xmlns" attributes have been sorted. The plain "xmlns" attribute comes first, followed by "xmlns:xsd" then "xmlns:xsi" (because "xsd" comes alphabetically before "xsi"). Then the non-xmlns attributes follow. (This method of sorting is obvious, isn't it?). See Sorting attributes below.

We have to leave two newlines after the </Document> tag to allow for the whitespace around the Signature element we have removed. This pedantry is sadly important. Note also that the o-slash character in Fødselsnummer (Date of birth) is encoded in UTF-8 form as C3 B8.

The SHA-1 digest value of this c14n data is (0x)cb150ccf1c5773f11176830a87cb1e005c961881 or yxUMzxxXc/ERdoMKh8seAFyWGIE= in base64. The c14n'd SignedInfo to be signed is

<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#" xmlns:xsd="http://www.w3.org/2001/XMLSchema.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></SignatureMethod>
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></Transform>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
<DigestValue>yxUMzxxXc/ERdoMKh8seAFyWGIE=</DigestValue>
</Reference>
</SignedInfo>

Note how the "xmlns:xxx" namespace attributes propagate down but that the default "xmldsig" xmlns attribute over-rides the "msghead" one from above (don't you just love XML namespaces and the way they make everything simple?). The SHA-1 digest value of this is (0x)cb150ccf1c5773f11176830a87cb1e005c961881 and the rsa-sha1 signature of it as signed by Alice's RSA key is

h3adYmb2htVm2tjt/QWfyBoW4QZo2eF/qwDtjpGMhe4Ea7ADx3qJ1TnL9T3+QinwCakolGGK2km/OMf3TQNn1o
qtfCtQjwsrm7YkZU3fJLPszhzKnpF5oYsb/gKIbKlXFAEHRGoC6dcl9Jm81BQaazPERylhmr+t+m7aH4rYbQk=

The final XML is

<?xml version="1.0" encoding="ISO-8859-1"?>
<MsgHead xmlns="http://www.example.com/msghead" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema.xsd" 
    xsi:schemaLocation="http://www.example.com/msghead MsgHead.xsd">
  <MsgInfo>
    <Type DN="Resept" V="ERM1"></Type>
    <Patient>
      <FamilyName>Nordmann</FamilyName>
      <GivenName>Ola</GivenName>
      <Ident>
        <Id>12345678901</Id>
        <TypeId DN="Fødselsnummer" S="2.16.123.1.10.4.1.1.9999" V="FNR"></TypeId>
      </Ident>
    </Patient>
  </MsgInfo>
  <Document>
    <RefDoc>foo bar</RefDoc>
  </Document>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>yxUMzxxXc/ERdoMKh8seAFyWGIE=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>
h3adYmb2htVm2tjt/QWfyBoW4QZo2eF/qwDtjpGMhe4Ea7ADx3qJ1TnL9T3+QinwCakolGGK2km/OMf3TQNn1o
qtfCtQjwsrm7YkZU3fJLPszhzKnpF5oYsb/gKIbKlXFAEHRGoC6dcl9Jm81BQaazPERylhmr+t+m7aH4rYbQk=
</SignatureValue>
<KeyInfo>
<KeyValue>
<RSAKeyValue>
<Modulus>
4IlzOY3Y9fXoh3Y5f06wBbtTg94Pt6vcfcd1KQ0FLm0S36aGJtTSb6pYKfyX7PqCUQ8wgL6xUJ5GRPEsu9gyz8
ZobwfZsGCsvu40CWoT9fcFBZPfXro1Vtlh/xl/yYHm+Gzqh0Bw76xtLHSfLfpVOrmZdwKmSFKMTvNXOFd0V18=
</Modulus>
<Exponent>AQAB</Exponent>
</RSAKeyValue>
</KeyValue>
</KeyInfo>
</Signature>
</MsgHead>

This should verify at the Online XML Digital Signature Verifer. All the above files are in the zip file msghead-enveloped.zip.

Definitions

From xmldsig-core:

Enveloping signature
The signature is over content found within an Object element of the signature itself. The Object (or its content) is identified via a Reference (via a URI fragment identifier or transform). <Ref>
Enveloped signature
The signature is over the XML content that contains the signature as an element. The content provides the root XML document element. Obviously, enveloped signatures must take care not to include their own value in the calculation of the SignatureValue. <Ref>

Sorting attributes

The c14n ordering of attributes is as follows.

  1. The default namespace declaration xmlns="...", if any, comes first.
  2. Namespace declarations, sorted by prefix (the part after "xmlns:"). So xmlns:a="http://www.w3.org" comes before xmlns:b="http://www.ietf.org".
  3. Unqualified attributes, sorted by name. So attr="..." comes before attr2="...".
  4. Qualified attributes, sorted by namespace URI then name. So b:attr="..." comes before a:attr="...", because we read this as http://www.ietf.org:attr="..." comes before http://www.w3.org:attr="...". And a:attr="..." comes before a:attr2="..."
<e xmlns="http://example.org" xmlns:a="http://www.w3.org" xmlns:b="http://www.ietf.org" attr="I'm" attr2="all" b:attr="sorted" a:attr="out" a:attr2="now"></e>

For an excellent explanation of the rules to sort attributes when canonicalizing your data for XML-DSIG, see Keith S. Beattie's article on attribute ordering KSB's XML C14N Notes.

References

Contact

For more information, or to comment on this page, please send us a message.

This page first published 9 May 2012. Last updated 13 August 2017