Skip to main content

Signing XML Documents

This topic 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.

For information on alternative digest, signature, and canonicalization methods, see Adding Digital Signatures.

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">
    
  • A <Signature> element includes a <Reference> element that points to that Id as follows:

    <Reference URI="#123456789">
    

    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® data platform 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 topic.

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>

To create digital signatures, you use the class %XML.Security.SignatureOpens in a new tab. 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.WriterOpens in a new tab 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 Setup and Other Common Activities.

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.SignatureOpens in a new tab 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
}

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.X509CredentialsOpens in a new tab that accesses the appropriate InterSystems IRIS credential set. To do so, call the GetByAlias() class method of %SYS.X509CredentialsOpens in a new tab.

    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.

  3. Create an instance of %XML.Security.SignatureOpens in a new tab 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
    

    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.ReferenceOpens in a new tab 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 
    
  8. Create an instance of %XML.DocumentOpens in a new tab 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.

    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.DocumentOpens in a new tab that you just created. The SignDocument() method uses information in that instance to update the signature object.

  10. Use %XML.WriterOpens in a new tab to generate output for the object. See 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)
}

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
}

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.X509CredentialsOpens in a new tab that accesses the appropriate InterSystems IRIS credential set. To do so, call the GetByAlias() class method of %SYS.X509CredentialsOpens in a new tab, as described in the preceding steps.

  3. Create an instance of %XML.Security.SignatureOpens in a new tab 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.X509DataOpens in a new tab, as follows:

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

    Where cred is the instance of %SYS.X509CredentialsOpens in a new tab 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)
    

    Where signature is the instance of %XML.Security.SignatureOpens in a new tab, and x509data is the instance of %XML.Security.X509DataOpens in a new tab.

  6. Create an instance of %XML.Security.ReferenceOpens in a new tab as follows:

    set algorithm=$$$SOAPWSEnvelopedSignature
    set reference=##class(%XML.Security.Reference).Create("",algorithm) 
    
  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 a digital 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 Setup and Other Common Activities.

Validating a Signature

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

  1. Create an instance of %XML.ReaderOpens in a new tab and use it to open the document.

    This class is discussed in Importing XML into Objects.

  2. Get the Document property of your reader. This is an instance of %XML.DocumentOpens in a new tab 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.SignatureOpens in a new tab. For example:

     do reader.Correlate("Signature","%XML.Security.Signature")
  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
        }
    

    The imported object is an instance of %XML.Security.SignatureOpens in a new tab.

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

     set status=isig.ValidateDocument(document)

    For more validation options, see the class reference for this method in %XML.Security.SignatureOpens in a new tab.

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 }
}

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.DocumentOpens in a new tab 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}
    
  • When you validate the document, include the following steps just before you call Correlate():

    1. Call the AddIDs() method of the %XML.DocumentOpens in a new tab 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")
    
FeedbackOpens in a new tab