
Extending XSLT to Encrypt XML on the Fly
by Doug Tidwell
08/13/2001
XSLT is an extremely powerful, flexible language for transforming XML
documents. Despite its power, there are times when we need to write
extension elements and extension functions to do things the
language was never designed to do. In this article, I'll take a look at
XSLT's extension mechanism by writing an extension function that encrypts
part of an XML document.
The good news is that the extension mechanism is defined in the XSLT 1.0
specification. Unfortunately, the specification doesn't define all of the
details about how extensions should work, so there are some inconsistencies
between processors. If you write an extension function or an extension
element that works with your favorite XSLT processor, no one can do
something sinister to prevent the extension from working. On the other
hand, if you decide to change the XSLT processor you're using, you'll
probably have to change some of your code.
Our example here is written in Java, using the interfaces defined by the
Xalan XSLT processor. As we write an extension function, we'll have to
figure out how to move data from the XSLT processor to our code and back.
(If you're not a Java hacker, the Java version of Xalan also supports
IBM's
Bean Scripting Framework. That lets you write extensions in JPython
[Jython], JavaScript, Perl Script, Jacl, and other scripting languages if
you prefer.)
A Document in Need of Encryption
To get started, let's look at a sample document. Here's an XML document
that represents a customer order:
<?xml version="1.0"?>
<customer_order>
<items>
<item>
<name>Turnip Twaddler</name>
<qty>3</qty>
<price>9.95</price>
</item>
<item>
<name>Snipe Curdler</name>
<qty>1</qty>
<price>19.95</price>
</item>
</items>
<customer>
<name>Doug Tidwell</name>
<street>1234 Main Street</street>
<city state="NC">Raleigh</city>
<zip>11111</zip>
</customer>
<credit_payment>
<card_issuer>American Express</card_issuer>
<card_number>1234 567890 12345</card_number>
<expiration_date month="10" year="2004"/>
</credit_payment>
</customer_order>
Glancing at our document, it's pretty clear that the
<credit_payment> element contains information we'd like to
keep secure. We'll use the element-wise encryption function shipped
with the XML Security Suite (XSS4J), a package of XML security utilities
available at IBM's alphaWorks Web site. This technology is built on the
Java Cryptography Extension (JCE) and the XML Digital Signatures work done
jointly by the W3C and the IETF. Using element-wise encryption, we can
convert our sample document into
code that looks something like this.
As you can see from the example, the <credit_payment>
element has been replaced with an <EncryptedData>
element. To anyone unfamiliar with the original document format, the
encrypted version offers no clues as to the original content of the
encrypted element. <EncryptedData> might represent a single
empty element with a couple of attributes; it might represent a single
element with lots of text; it might represent an element which contained
multiple levels of descendants beneath it. Without the appropriate keys to
unencrypt your data, the data is just so much garbage.
Designing Our Extension Function
What we'd like to do is write an extension function so that we can use
an XSLT stylesheet to encrypt various elements. This gives us several
advantages:
If we want to encrypt other portions of the document, we can do that
with simple changes to our stylesheet.
We can add logic to our stylesheet so that different portions of the
document will be encrypted for different users. For example, my medical
history might be encrypted unless you work in the medical department, and
my salary history might be encrypted unless you work in Human
Resources.
We can keep all of the details of our encryption technology on the
server. A client that receives the partially encrypted document above has
no idea if this document was partially encrypted on demand, whether other
users see a similarly encrypted document, or what the original document
looked like.
Visit xml.oreilly.com for a
complete list of O'Reilly books about XML technologies.
We want to create an extension function that takes as an argument the
node of the XML document that we want to encrypt, along with whatever
parameters we need to pass to the cryptographic routines. Here is what our
extension function call looks like:
<xsl:template match="credit_payment">
<xsl:copy-of select="encrypt:encryptNode(., 'storepass',
'keystore', 'key', 'crypto-details.xml')"/>
</xsl:template>
We're passing several things to our function. The first is the current
node (represented by the compact XPath expression '.'). Next is the
password to JCE keystore, followed by the name of the keystore file
(cleverly named keystore here), and the alias of the key (named
key, continuing our naming convention). The last item we pass to
the function is the name of a configuration file used by XSS4J. Here's what
that file looks like:
<?xml version="1.0"?>
<EncryptedData xmlns="http://www.w3.org/2000/11/temp-xmlenc"
Type="Element">
<EncryptedKey>
<EncryptionMethod Algorithm="urn:rsadsi-com:rsa-v1.5"/>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<KeyName>key</KeyName>
</KeyInfo>
</EncryptedKey>
<EncryptionMethod Algorithm="urn:nist-gov:tripledes-ede-cbc"/>
</EncryptedData>
This file defines various details about the cryptographic algorithms
used to encrypt the data. In this case, we're using a randomly generated
TripleDES key, which is encrypted with our RSA key (this is also known as
key transport). This configuration file is one of the requirements
of the XSS4J classes we're using. Specifying these details in a separate
file gives us more flexibility in implementing encryption; if we want to
change the algorithms we're using, we simply modify the XML file listed
above.
At this point, we've defined the arguments to our extension function.
We chose these arguments based on the requirements of the XSS4J classes
we're using to encrypt the data, and based on the structure of the document
we're encrypting. The only thing left is the minor detail of writing the
code.
Writing Our Extension Function
Our final step is to actually write the code that will transform the
appropriate part of the XML document. We've already looked at the method
signature in the stylesheet; here's a table that lists the arguments, their
datatype in the stylesheet, and their datatype in the extension
function:
| Extension Function Parameter |
XSLT Datatype |
Java Datatype |
| . [The current node] |
An XPath node-set |
org.w3c.dom.NodeList |
'storepass' |
A string |
A string |
'keystore' |
A string |
A string |
'key' |
A string |
A string |
'crypto-details.xml' |
A string |
A string |
Most of the datatype conversions are straightforward, since we're
passing mostly string data back and forth. The NodeList object is
more complex, however. Part of the Document Object Model (DOM) standard, a
NodeList is an ordered set of document nodes. We'll use one of
these nodes (there's only one in our case, but there could be many with a
different XML document) as one of the inputs to the
XEncryption.encrypt() function.
Here's the actual signature of our extension function:
public static XNodeSet encryptNode(NodeList nl, String passPhrase,
String keyStore, String keyName,
String encryptionTemplate)
You can see how the arguments to our function match the table above.
Now that our extension function has the data it needs from the XSLT
processor, we need to invoke the XEncryption.encrypt()
function, the code that actually does the work of encrypting our data. The
version of the function we'll be using takes five parameters:
XEncryption.encrypt()
Parameter
Comes from
The data to be encrypted
The XSLT processor. We get a
NodeList, and the first
(and only, in this case) node in the list is
what we want to encrypt.
A flag indicating whether just the element's
content should be encrypted
It's always false.
A DOM
Element representing the
<EncryptedData> element from our configuration file
Parsing the XML configuration file. The name of
the file is passed to us by the XSLT processor.
A Java Key
object (java.security.Key)
A Java keystore. We get the password of the
keystore, the name of the keystore, and the name of the key alias
from the XSLT processor.
A DOM Element representing the
<EncryptedKey> element from
our configuration file
Parsing the XML configuration file. Again, we get the name of
this file from the XSLT processor.
At this point, we simply gather the five parameters we need and call
encrypt(). The first two items in the table above are given, so we
can move on to the third. We'll create a DOMParser object to parse
the XML configuration file. Remember, the root element of that file was an
<EncryptedData> element, so getting the root of the DOM tree
will give us the third parameter we need. Here's how the code looks:
DOMParser parser = new DOMParser();
parser.setIncludeIgnorableWhitespace(false);
parser.parse(encryptionTemplate);
doc = parser.getDocument();
ee = doc.getDocumentElement();
Element ek =
(Element)(ee.getElementsByTagName("EncryptedKey").item(0));
In this code, the document element of the DOM tree is a DOM
Element that represents the <EncryptedData> element.
We also get its first <EncryptedKey> element; we'll need
that as the fifth parameter to XEncryption.encrypt(). (The DOM
getElementsByTagName method returns a NodeList of
all the child elements with a given name. We take the first one from the
NodeList and save it for later.)
The only thing left is the key from the Java keystore. We need to open
the keystore using the keystore password we were given by the XSLT
processor. Once we've opened the specified file, we'll try to retrieve the
key whose alias we received. If any of these steps fail, our code will
throw an exception and stop. Here's the actual code:
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keyStore), passPhrase.toCharArray());
Key k = null;
if (ks.isKeyEntry(keyName))
k = (ks.getCertificate(keyName)).getPublicKey();
In this sample, we create a Java keystore (that's what JKS
stands for), then attempt to load the keystore using the filename and
password given to us by the XSLT processor. Once the keystore has been
loaded, we attempt to get the public key whose name we received from the
processor.
At this point, we have all the data we need to invoke
XEncryption.encrypt(). Here are the magic lines of code that do
this:
encrypted = xenc.encrypt((Element)nl.item(0), false, ee, k, ek);
encryptedResult = new XNodeSet(encrypted);
In this listing, encrypted is a DOM Element. We use
this to create an XNodeSet; the XNodeSet is a
Xalan-specific class that represents an XPath node-set. We then return the
XNodeSet, and we're done.
return encryptedResult;
The output of our transformation is the encrypted document we looked at
earlier. The <credit_payment> element has been
encrypted. This technology brings up an interesting scenario. Today, if I
go to a Web site and order something, I'm comfortable sending my credit
card number across a secure socket. In doing so, of course, I'm trusting
that the employees of the online merchant won't do something improper with
my credit card number. What if I used my private key and American Express's
public key to encrypt my credit card information? The merchant would be
unable to view any of the data; they would simply pass it on to American
Express for verification. American Express could use its private key and my
public key to unencrypt the data, validate that it actually came from me,
then return an authorization code to the online merchant.
Summary
Now that the code is done, we have a powerful extension function we can
invoke in our XSLT stylesheets. As I mentioned earlier, we can use logic
in our stylesheets to change which parts of the document (if any) are
encrypted for particular users or in certain circumstances. Most extension
functions are written similarly: we need to use functionality available in
some external package, and our work in writing the extension function
involves gathering the appropriate data from the XSLT environment, passing
it to our extension function, generating the appropriate results, then
getting that data back to the XSLT environment. Once you're comfortable
writing extension functions to integrate other code with the XSLT
processor, you'll be amazed at what your stylesheets can do.
Resources
Here's a list of various resources that might be useful to you:
My O'Reilly book, XSLT, contains an extensive discussion of the
XSLT extension mechanism, with many more examples of both extension
functions and extension elements. See XSLT for more information.
The XML Security Suite is available at IBM's alphaWorks site.
It comes with a variety of sample applications, the DOMHASH algorithm, and
code to translate between XML and ASN.1, and vice versa. XSS4J was created
at IBM's Tokyo Research Lab by the same team that created what ultimately
became the Xerces parser.
The Xalan stylesheet engine is available at the Apache XML project. The
Xerces parser used in this article is the xerces.jar that ships
with Xalan.
Find out more about the Java Cryptography Extension at java.sun.com/products/jce. You can find a list of
companies and organizations that offer cryptographic service providers at
this site as well.
Information about the IETF and W3C's work on digital signatures is also available.
Click on Code Listings to view all the
code in the examples discussed in this article.
Doug Tidwell is a senior programmer at IBM. He has more than a
sixth of a century of programming experience and has been working with
markup languages for more than a decade. He was a speaker at the
first XML conference in 1997 and has taught XML classes around the
world. His job as a Cyber Evangelist is to look busy and to help
people use new technologies to solve problems. Doug also writes regularly for
IBM's developerWorks
site on XML and Web Services topics. Using a pair of zircon-encrusted
tweezers, he holds a master's degree in computer science from Vanderbilt
University and a bachelor's degree in English from the University of Georgia.
He lives in
Raleigh, North Carolina, with his wife, cooking teacher Sheri Castle (see her
Web site at
www.sheri-inc.com), and
their daughter Lily.

|