Using Caché XML Tools
Signing XML Documents
[Home] [Back] [Next]
InterSystems: The power behind what matters   
Class Reference   
Search:    

This chapter describes how to add digital signatures to XML documents. It discusses the following topics:

Tip:
You might find it useful to enable SOAP logging in this namespace so that you receive more information about any errors; see Caché SOAP Log in Troubleshooting Caché SOAP Problems in the book Creating Web Services and Web Clients in Caché.
For information on alternative digest, signature, and canonicalization methods, see Adding Digital Signatures in Securing Caché 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:
Note:
Caché 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>
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 Caché credential set. A Caché credential set is an alias for the following set of information, stored in the system manager’s database:
For details, see the chapter Setup and Other Common Activities in Securing Caché Web Services.
Requirements of the XML-Enabled Class
The XML-enabled class must include the following:
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.X509Credentials that accesses the appropriate Caché credential set. To do so, call the GetByAlias() class method of %SYS.X509Credentials.
    classmethod GetByAlias(alias As %String, pwd As %String) as %SYS.X509Credentials
    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 Caché 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
    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:
  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.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 Caché 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.X509Credentials that accesses the appropriate Caché 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)
    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)
    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) 
  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
Before you can validate digital signatures, you must provide trusted certificates for Caché to use. Caché can validate a signature if it uses a certificate that is signed by an entity whose own certificate, in turn, is trusted by Caché.
For details, see the chapter Setup and Other Common Activities in Securing Caché 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 Caché 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")
  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.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)
    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 }
}
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. Caché 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: