Encrypting XML Documents
This topic describes how to encrypt XML documents.
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.
About Encrypted XML Documents
An encrypted XML document includes the following elements:
-
An <EncryptedData> element, which includes encrypted data encrypted by a randomly generated symmetric key. (It is more efficient to encrypt with a symmetric key than with a public key.)
-
At least one <EncryptedKey> element. Each <EncryptedKey> element carries an encrypted copy of the symmetric key that was used to encrypt the data; it also includes an X.509 certificate with a public key. The recipient who has the matching private key can decrypt the symmetric key and then decrypt the <EncryptedData> element.
-
(Optional) Other elements in clear text.
The following shows an example:
<?xml version="1.0" encoding="UTF-8"?>
<Container xmlns="http://www.w3.org/2001/04/xmlenc#">
<EncryptedKey>
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
<DigestMethod xmlns="http://www.w3.org/2000/09/xmldsig#"
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
</EncryptionMethod>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<X509Data>
<X509Certificate>MIICnDCCAYQCAWUwDQYJKo... content omitted</X509Certificate>
</X509Data>
</KeyInfo>
<CipherData>
<CipherValue>J2DjVgcB8vQx3UCy5uejMB ... content omitted</CipherValue>
</CipherData>
<ReferenceList>
<DataReference URI="#Enc-E0624AEA-9598-4436-A154-F746B07A2C55"></DataReference>
</ReferenceList>
</EncryptedKey>
<EncryptedData Id="Enc-E0624AEA-9598-4436-A154-F746B07A2C55"
Type="http://www.w3.org/2001/04/xmlenc#Content">
<EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc">
</EncryptionMethod>
<CipherData>
<CipherValue>LmoBK7+nDelTOsC3 ... content omitted</CipherValue>
</CipherData>
</EncryptedData>
</Container>
To create an encrypted document, you use the classes %XML.Security.EncryptedDataOpens in a new tab and %XML.Security.EncryptedKeyOpens in a new tab. These XML-enabled classes project to valid <EncryptedData> and <EncryptedKey> elements in the appropriate namespace.
Creating an Encrypted XML Document
The easiest way to create an encrypted XML document is as follows:
-
Define and use a general-purpose container class that can be projected directly to the desired XML document.
-
Create a stream that contains the XML that you will encrypt.
-
Encrypt that stream and write it, along with the corresponding encryption keys, to the appropriate properties of the container class.
-
Generate XML output for your container class.
Prerequisites for Encryption
Before you can encrypt a document, you must create an InterSystems IRIS® data platform credential set that contains the certificate of the entity to whom you are sending the encrypted document. In this case, you do not need (and should not have) the associated private key. For details, see Setup and Other Common Activities.
Requirements of the Container Class
A general-purpose container class must include the following:
-
A property of type %XML.Security.EncryptedDataOpens in a new tab that is projected as the <EncryptedData> element.
This property will carry the encrypted data.
-
At least one property of type %XML.Security.EncryptedKeyOpens in a new tab that is projected as the <EncryptedKey> element.
These properties will carry the corresponding key information.
The following shows an example:
Class XMLEncryption.Container Extends (%RegisteredObject, %XML.Adaptor)
{
Property Data As %XML.Security.EncryptedData (XMLNAME="EncryptedData");
Property Key As %XML.Security.EncryptedKey (XMLNAME="EncryptedKey");
Parameter NAMESPACE = "http://www.w3.org/2001/04/xmlenc#";
//methods
}
Generating an Encrypted XML Document
To generate and write an encrypted document, do the following:
-
Create a stream that contains an XML document.
To do this, you typically use %XML.WriterOpens in a new tab to write output for an XML-enabled object to a stream.
-
Create at least one instance of %SYS.X509CredentialsOpens in a new tab that accesses the InterSystems IRIS credential set of the entity to whom you are going to give the encrypted document. To do so, call the GetByAlias() class method of this class. For example:
set credset=##class(%SYS.X509Credentials).GetByAlias("recipient")
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.
-
Create at least one instance of %XML.Security.EncryptedKeyOpens in a new tab. To create an instance of this class, use the CreateX509() class method of this class. For example:
set enckey=##class(%XML.Security.EncryptedKey).Createx509(credset,encryptionOptions,referenceOption)
-
credset is the instance of %SYS.X509CredentialsOpens in a new tab that you just created.
-
encryptionOptions is $$$SOAPWSIncludeNone (there are other options, but they do not apply in this scenario).
This macro is defined in the %soap.inc include file.
-
referenceOption specifies the nature of the reference to the encrypted element. For permitted values, see Reference Options for X.509 Certificates.
The macros used here are defined in the %soap.inc include file.
-
-
Create an instance of %Library.ListOfObjectsOpens in a new tab and use its Insert() method to insert the instances of %XML.Security.EncryptedKeyOpens in a new tab that you just created.
-
Create an instance of %XML.Security.EncryptedDataOpens in a new tab by using the %New() method. For example:
set encdata=##class(%XML.Security.EncryptedData).%New()
-
Use the EncryptStream() instance method of %XML.Security.EncryptedDataOpens in a new tab to encrypt the stream that you created in step 2. For example:
set status=encdata.EncryptStream(stream,encryptedKeys)
-
stream is the stream that you created in step 1.
-
encryptedKeys is the list of keys that you created in step 4.
-
-
Create and update an instance of your container class.
-
Write the list of keys to the appropriate property of this class.
-
Write the instance of %XML.Security.EncryptedDataOpens in a new tab to the appropriate property of this class.
The details depend on your class.
-
-
Use %XML.WriterOpens in a new tab to generate output for your container class. See Writing XML Output from Objects.
For example, the container class shown previously also includes the following method:
ClassMethod Demo(filename = "",obj="")
{
#include %soap
if (obj="") {
set obj=##class(XMLEncryption.Person).GetPerson()
}
//create stream from this XML-enabled object
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 }
set status=writer.RootObject(obj)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
do stream.Rewind()
set container=..%New() ; this is the object we will write out
set cred=##class(%SYS.X509Credentials).GetByAlias("servercred")
set parts=$$$SOAPWSIncludeNone
set ref=$$$KeyInfoX509Certificate
set key=##class(%XML.Security.EncryptedKey).CreateX509(cred,parts,ref)
set container.Key=key ; this detail depends on the class
//need to create a list of keys (just one in this example)
set keys=##class(%Collection.ListOfObj).%New()
do keys.Insert(key)
set encdata=##class(%XML.Security.EncryptedData).%New()
set status=encdata.EncryptStream(stream,keys)
set container.Data=encdata ; this detail depends on the class
// write output for the container
set writer=##class(%XML.Writer).%New()
set writer.Indent=1
if (filename'="") {
set status=writer.OutputToFile(filename)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
}
set status=writer.RootObject(container)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
}
This method can accept the OREF of any XML-enabled class; if none is provided, a default is used.
Decrypting an Encrypted XML File
Prerequisites for Decryption
Before you can decrypt an encrypted XML document, you must provide both of the following:
-
Trusted certificates for InterSystems IRIS to use.
-
An InterSystems IRIS credential set whose private key matches the public key used in the encryption.
For details, see Setup and Other Common Activities.
Decrypting the Document
To decrypt an encrypted XML document, do the following:
-
Create an instance of %XML.ReaderOpens in a new tab and use it to open the document.
-
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.
-
Use the Correlate() method of your reader to correlate the <EncryptedKey> element or elements with the class %XML.Security.EncryptedKeyOpens in a new tab. For example:
do reader.Correlate("EncryptedKey","%XML.Security.EncryptedKey")
-
Iterate through the document to read the <EncryptedKey> 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(.ikey,.status) { write !,"Unable to import key",! do $system.OBJ.DisplayError(status) quit }
The imported object is an instance of %XML.Security.EncryptedKeyOpens in a new tab.
-
Create an instance of %Library.ListOfObjectsOpens in a new tab and use its Insert() method to insert the instances of %XML.Security.EncryptedKeyOpens in a new tab that you just obtained from the document.
-
Call the ValidateDocument() method of the class %XML.Security.EncryptedDataOpens in a new tab.
set status=##class(%XML.Security.EncryptedData).ValidateDocument(.doc,keys)
The first argument, returned by reference, is the modified version of the DOM that you retrieved in step 2. The second argument is the list of keys from the previous step.
-
Optionally use %XML.WriterOpens in a new tab to generate output for the modified DOM. See Writing XML Output from Objects.
For example:
ClassMethod DecryptDoc(filename As %String)
{
#include %soap
set reader=##class(%XML.Reader).%New()
set status=reader.OpenFile(filename)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
set doc=reader.Document
//get <Signature> element
do reader.Correlate("EncryptedKey","%XML.Security.EncryptedKey")
if 'reader.Next(.ikey,.status) {
write !,"unable to import key",!
do $system.OBJ.DisplayError(status)
quit
}
set keys=##class(%Collection.ListOfObj).%New()
do keys.Insert(ikey)
// the following step returns the decrypted document
set status=##class(%XML.Security.EncryptedData).ValidateDocument(.doc,keys)
set writer=##class(%XML.Writer).%New()
set writer.Indent=1
do writer.Document(doc)
quit $$$OK
}