Skip to main content
Previous sectionNext section

Signing XML Documents

This chapter describes how to add digital signatures to XML documents.

Tip:

You might find it useful to enable SOAP logging in this namespace so that you receive more information about any errors; see “InterSystems IRIS SOAP Log” in “Troubleshooting SOAP Problems in InterSystems IRIS” in the book Creating Web Services and Web Clients.

For information on alternative digest, signature, and canonicalization methods, see “Adding Digital Signatures” in Securing Web Services.

About Digitally Signed Documents

A digitally signed XML document includes one or more <Signature> elements, each of which is a digital signature. Each <Signature> element signs a specific element in the document as follows:

  • Each signed element has an Id attribute, which equals some unique value. For example:

    <Person xmlns="http://mynamespace" Id="123456789">
    Copy code to clipboard
  • A <Signature> element includes a <Reference> element that points to that Id as follows:

    <Reference URI="#123456789">
    Copy code to clipboard

    The <Signature> element is signed by a private key. This element includes an X.509 certificate signed by a signing authority. If the recipient of the signed document trusts this signing authority, the recipient can then validate the certificate and use the contained public key to validate the signature.

Note:

InterSystems IRIS also supports a variation in which the signed elements have an attribute named ID rather than Id. For information, see the last section in this chapter.

The following shows an example, with whitespace added for readability:

<?xml version="1.0" encoding="UTF-8"?>
<Person xmlns="http://mynamespace" Id="123456789">
  <Name>Persephone MacMillan</Name>
  <DOB>1976-02-20</DOB>
  <s01:Signature xmlns="http://www.w3.org/2000/09/xmldsig#" 
                 xmlns:s01="http://mynamespace" 
                 s02:Id="Id-BC0B1674-758D-40B9-84BF-F7BAA3AA19F4" 
xmlns:s02="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <SignedInfo>
      <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
      </CanonicalizationMethod>
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1">
      </SignatureMethod>
      <Reference URI="#123456789">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature">
          </Transform>
          <Transform Algorithm="http://www.w3.org/TR/2001/REC-xml1317c14n-20010315">
          </Transform>
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
        <DigestValue>FHwW2U58bztLI4cIE/mp+nsBNZg=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>MTha3zLoj8Tg content omitted</SignatureValue>
    <KeyInfo>
      <X509Data>
        <X509Certificate>MIICnDCCAYQCAWUwDQYJ content omitted</X509Certificate>
      </X509Data>
    </KeyInfo>
  </s01:Signature>
</Person>
Copy code to clipboard

To create digital signatures, you use the class %XML.Security.Signature. This is an XML-enabled class whose projection is a valid <Signature> element in the appropriate namespace.

Creating a Digitally Signed XML Document

To create a digitally signed XML document, you use %XML.Writer to generate output for one or more appropriately defined XML-enabled objects.

Before you generate output for the objects, you must create the needed signatures and write them to the objects, so that the information is available to be written to the destination.

Prerequisites for Signing

Before you can sign a document, you must create at least one InterSystems IRIS credential set. An InterSystems IRIS credential set is an alias for the following set of information, stored in the system manager’s database:

  • A certificate, which contains a public key. The certificate should be signed by a signing authority that is trusted by the recipient of the document.

  • The associated private key, which InterSystems IRIS uses when needed but never sends.

    The private key is needed for signing.

  • (Optional) The password for the private key, which InterSystems IRIS uses when needed but never sends. You can either load the private key or you can supply it at runtime.

For details, see the chapter “Setup and Other Common Activities” in Securing Web Services.

Requirements of the XML-Enabled Class

The XML-enabled class must include the following:

  • A property that is projected as the Id attribute.

  • At least one property of type %XML.Security.Signature that is projected as the <Signature> element. (An XML document can contain multiple <Signature> elements.)

Consider the following class:

Class XMLSignature.Simple Extends (%RegisteredObject, %XML.Adaptor)
{

Parameter NAMESPACE = "http://mynamespace";

Parameter XMLNAME = "Person";

Property Name As %String;

Property DOB As %String;

Property PersonId As %String(XMLNAME = "Id", XMLPROJECTION = "ATTRIBUTE");

Property MySig As %XML.Security.Signature(XMLNAME = "Signature");

//methods
}
Copy code to clipboard

Generating and Adding the Signature

To generate and add the digital signature, do the following:

  1. Optionally include the %soap.inc include file, which defines macros you might need to use.

  2. Create an instance of %SYS.X509Credentials that accesses the appropriate InterSystems IRIS credential set. To do so, call the GetByAlias() class method of %SYS.X509Credentials.

    classmethod GetByAlias(alias As %String, pwd As %String) as %SYS.X509Credentials
    
    • alias is the alias for the certificate.

    • pwd is the private key password. The private key password is needed only if the associated private key is encrypted and if the password was not loaded when the private key file was loaded.

    To run this method, you must be logged in as a user included in the OwnerList for that credential set, or the OwnerList must be null. Also see “Retrieving a Credential Set Programmatically” in Securing Web Services.

  3. Create an instance of %XML.Security.Signature that uses the given credential set. To do so, call the CreateX509() class method of that class:

    classmethod CreateX509(credentials As %SYS.X509Credentials,              signatureOption As %Integer,              referenceOption As %Integer) as %XML.Security.Signature
    
    • credentials is the instance of %SYS.X509Credentials that you just created.

    • signatureOption is $$$SOAPWSIncludeNone (there are other options, but they do not apply in this scenario)

    • referenceOption specifies the nature of the reference to the signed element. For permitted values, see “Reference Options for X.509 Certificates” in Securing Web Services.

    The macros used here are defined in the %soap.inc include file.

  4. Get the value of the Id attribute, for the Id to which this signature will point.

    This detail depends on the definition of your XML-enabled object.

  5. Create an instance of %XML.Security.Reference to point to that Id. To do so, call the Create() class method of that class:

    ClassMethod Create(id As %String,                    algorithm As %String,                    prefixList As %String)
    

    id is the Id to which this reference should point.

    algorithm should be one of the following:

    • $$$SOAPWSEnvelopedSignature_","_$$$SOAPWSexcc14n — Use this version for exclusive canonicalization.

    • $$$SOAPWSEnvelopedSignature — This is equivalent to the preceding option.

    • $$$SOAPWSEnvelopedSignature_","_$$$SOAPWSexcc14n — Use this version for inclusive canonicalization.

  6. For your signature object, call the AddReference() method to add this reference to the signature:

    Method AddReference(reference As %XML.Security.Reference)
    
  7. Update the appropriate property of your XML-enabled class to contain the signature.

    This detail depends on your XML-enabled class. For example:

    set object.MySig=signature 
    Copy code to clipboard
  8. Create an instance of %XML.Document that contains your XML-enabled object serialized as XML.

    This is necessary because the signature must include information about the signed document.

    See “Example 2: Converting an Object to a DOM,” earlier in this book.

    Note:

    This document does not contain whitespace.

  9. Call the SignDocument() method of your signature object:

    Method SignDocument(document As %XML.Document) As %Status
    

    The argument for this method is the instance of %XML.Document that you just created. The SignDocument() method uses information in that instance to update the signature object.

  10. Use %XML.Writer to generate output for the object. See the chapter “Writing XML Output from Objects.”

    Note:

    The output that you generate must include the same whitespace (or lack of whitespace) as contained in the document used in the signature. The signature contains a digest of the document, and the digest will not match the document if you set the Indent property to 1 in the writer.

For example:

Method WriteSigned(filename As %String = "")
{
#Include %soap
    //create a signature object
    set cred=##class(%SYS.X509Credentials).GetByAlias("servercred")
    set parts=$$$SOAPWSIncludeNone
    set ref=$$$KeyInfoX509Certificate
    
    set signature=##class(%XML.Security.Signature).CreateX509(cred,parts,ref,.status)
    if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}

    // get the Id attribute of the element we will sign;
    set refid=$this.PersonId ; this detail depends on structure of your classes
    
    // then create a reference to that Id within the signature object
    set algorithm=$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSc14n
    set reference=##class(%XML.Security.Reference).Create(refid,algorithm)
    do signature.AddReference(reference)

    //set the MySig property so that $this has all the information needed
    //when we generate output for it
    set $this.MySig=signature ; this detail depends on structure of your classes
    
    //in addition to $this, we need an instance of %XML.Document
    //that contains the object serialized as XML
    set document=..GetXMLDoc($this)

    //use the serialized XML object to sign the document
    //this updates parts of the signature
    set status=signature.SignDocument(document)
    if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}

    // write output for the object
    set writer=##class(%XML.Writer).%New()
    if (filename'="") {
        set status=writer.OutputToFile(filename)
        if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
    }
    do writer.RootObject($this)
}
Copy code to clipboard

The preceding instance method uses the following generic class method, which can be used with any XML-enabled object:

ClassMethod GetXMLDoc(object) As %XML.Document
{
    //step 1 - write object as XML to a stream
    set writer=##class(%XML.Writer).%New()
    set stream=##class(%GlobalCharacterStream).%New()
    set status=writer.OutputToStream(stream)
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
    set status=writer.RootObject(object)
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}

    //step 2 - extract the %XML.Document from the stream
    set status=##class(%XML.Document).GetDocumentFromStream(stream,.document)
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
    quit document
}
Copy code to clipboard

Variation: Digital Signature with URI="" in Reference

As a variation, the <Reference> element for a signature can have URI="", which is a reference to the root node of the XML document that contains the signature. To create a digital signature this way:

  1. Optionally include the %soap.inc include file, which defines macros you might need to use.

  2. Create an instance of %SYS.X509Credentials that accesses the appropriate InterSystems IRIS credential set. To do so, call the GetByAlias() class method of %SYS.X509Credentials, as described in the preceding steps.

  3. Create an instance of %XML.Security.Signature that uses the given credential set. To do so, call the CreateX509() class method of that class, as described in the preceding steps.

  4. Create an instance of %XML.Security.X509Data, as follows:

    set valuetype=$$$KeyInfoX509SubjectName_","_$$$KeyInfoX509Certificate
    set x509data=##class(%XML.Security.X509Data).Create(valuetype,cred)
    Copy code to clipboard

    Where cred is the instance of %SYS.X509Credentials that you previously created. These steps create an <X509Data> element that contains an <X509SubjectName> element and an <X509Certificate> element.

  5. Add the <X509Data> element to the <KeyInfo> element of the signature, as follows:

    do signature.KeyInfo.KeyInfoClauseList.Insert(x509data)
    Copy code to clipboard

    Where signature is the instance of %XML.Security.Signature, and x509data is the instance of %XML.Security.X509Data.

  6. Create an instance of %XML.Security.Reference as follows:

    set algorithm=$$$SOAPWSEnvelopedSignature
    set reference=##class(%XML.Security.Reference).Create("",algorithm) 
    Copy code to clipboard
  7. Continue the preceding steps at step 6 (calling AddReference()).

Validating a Digital Signature

For any digitally signed document that you receive, you can validate the signatures. You do not need to have an XML-enabled class that matches the document contents.

Prerequisites for Validating Signatures

To validate digital a signature, you must first provide a trusted certificate to InterSystems IRIS for the signer. InterSystems IRIS can validate a signature if it can verify the signer’s certificate chain from the signer’s own certificate to a self-signed certificate from a certificate authority (CA) that is trusted by InterSystems IRIS, including intermediate certificates (if any).

For details, see the chapter “Setup and Other Common Activities” in Securing Web Services.

Validating a Signature

To validate the signatures in a digitally signed XML document, do the following:

  1. Create an instance of %XML.Reader and use it to open the document.

    This class is discussed in the chapter “Importing XML into Objects,” earlier in this book.

  2. Get the Document property of your reader. This is an instance of %XML.Document that contains the XML document as DOM.

  3. Use the Correlate() method of your reader to correlate the <Signature> element or elements with the class %XML.Security.Signature. For example:

     do reader.Correlate("Signature","%XML.Security.Signature")
    Copy code to clipboard
  4. Iterate through the document to read the <Signature> element or elements. To do this, you use the Next() method of the reader, which returns an imported object, if any, by reference. For example:

    if 'reader.Next(.isig,.status) {
        write !,"Unable to import signature",!
        do $system.OBJ.DisplayError(status)
        quit
        }
    Copy code to clipboard

    The imported object is an instance of %XML.Security.Signature.

  5. Call the ValidateDocument() method of the imported signature. The argument to this method must be the instance of %XML.Document that you retrieved earlier.

     set status=isig.ValidateDocument(document)
    Copy code to clipboard

    For more validation options, see the class reference for this method in %XML.Security.Signature.

For example:

ClassMethod ValidateDoc(filename As %String) 
{
    set reader=##class(%XML.Reader).%New()
    set status=reader.OpenFile(filename)
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
    
    set document=reader.Document
    //get <Signature> element
    //assumes there is only one signature
    do reader.Correlate("Signature","%XML.Security.Signature")
    if 'reader.Next(.isig,.status) {
        write !,"Unable to import signature",!
        do $system.OBJ.DisplayError(status)
        quit
    }
    set status=isig.ValidateDocument(document)
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
}
Copy code to clipboard

Variation: Digital Signature That References an ID

In the typical case, a <Signature> element includes a <Reference> element that points to a unique Id elsewhere in the document. InterSystems IRIS also supports a variation in which the <Reference> element points to an attribute named ID rather than Id. In this variation, extra work is needed to sign the document and to validate the document.

To digitally sign the document, follow the steps in “Creating a Digitally Signed XML Document,” with the following changes:

  • For the XML-enabled class, include a property that is projected as the ID attribute rather than the Id attribute.

  • When you generate and add the signature, call the AddIDs() method of the %XML.Document instance. Do this after you obtain the serialized XML document and before you call the SignDocument() method of the signature object. For example:

        //set the MySig property so that $this has all the information needed
        //when we generate output for it
        set $this.MySig=signature    ; this detail depends on structure of your classes
    
        //in addition to $this, we need an instance of %XML.Document 
        //that contains the object serialized as XML
        set document=..GetXMLDoc($this)
    
        //***** added step when signature references an ID attribute *****
        do document.AddIDs()
        
        //use the serialized XML object to sign the document
        //this updates parts of the signature 
        set status=signature.SignDocument(document)
        if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
    
    Copy code to clipboard
  • When you validate the document, include the following steps just before you call Correlate():

    1. Call the AddIDs() method of the %XML.Document instance

    2. Call the Rewind() method of the XML reader.

    For example:

        set document=reader.Document
        
        //added steps when signature references an ID attribute
        do document.AddIDs()
        do reader.Rewind()
    
        //get <Signature> element
        do reader.Correlate("Signature","%XML.Security.Signature")
    
    Copy code to clipboard