Implementing XML Encryption in Java
In the first column of this series, we presented a high-level WSS4J API, which we wish to implement in this series of columns. In the second column, we discussed the various Java components we are going to use in implementing the WSS4J API. The most important component is XML Security Suite for Java (XSS4J) from IBM alphaWorks. The second column also demonstrated how to use XSS4J for XML encryption.
In this column, we will use the XSS4J concepts discussed in the previous column to implement the XML encryption features in our WSS4J API.
Before we start the discussion, we would like to point out a name clash issue. There's an open source Web Services Security project at SourceForge.net (recently moved to Apache's web site) that shares the same WSS4J name. The WSS4J API we are developing in this series of columns is purely for educational purposes and aims at demonstrating the use of WSS in Java applications. The WSS4J open source project at SourceForge (now at Apache) is an independent project and is not part of our effort.
We are going to add support for both symmetric and asymmetric keys in our WSS4J API. Listing 1 shows a simple SOAP message we will use as sample input to our XML encryption process. We will encrypt a portion of Listing 1 to demonstrate the working of our WSS4J API.
We are going to perform three types of XML encryption. Listings 2, 3, and 4 are three WSS messages that show the outcome of our encryption process. Listings 2, 3, and 4 are slightly different from each other. As we have already covered the details of WSS format in our Web Services Security series (especially in Part 4), we will not go into the details of WSS format. Here we will only highlight the differences between Listings 2, 3, and 4:
Listing 2 shows the following:
Parameter element of Listing 1 has been
replaced by an EncryptedData element in Listing 2. The
EncryptedData element contains the encrypted form of the
Parameter element. The encryption algorithm used for this XML
encryption is triple DES as specified by the EncryptionMethod
child of the EncryptedData element. EncryptedData element in Listing 2 does not contain any key
information. The corresponding key information resides inside the
wsse:Security element in the SOAP header, which contains an
EncryptedKey element. The EncryptedKey element wraps
a symmetric triple DES secret key in encrypted form. Notice from Listing 2 that the
EncryptionMethod used to produce the EncryptedKey is
RSA and the key used for this RSA encryption is the tour operator's public
key. We covered the details of such encrypted keys in Step 2 of the Using
XSS4J for XML Encryption section in the second column. EncryptedData element with its
corresponding key used for encryption? Look at the ReferenceList
element inside the EncryptedKey element of Listing 2. The
ReferenceList element has a child element named
DataReference, whose URI attribute points to the
EncryptedData element we produced using the encrypted key.
The EncryptedKey element is a security token in WSS terms.
Although the XML encryption specification simply calls it an encryption key, the WSS specification talks about abstract (or more general) tokens. This
means we can treat cryptographic keys as a type of security token. Therefore,
while implementing WSS, we will treat an EncryptedKey structure as
a type of token and call it an encrypted key token.
Now look at Listing 3, which is a very
slightly modified form of Listing 2. The
only difference between Listings 2 and 3 is that the EncryptedData element in Listing 3
contains a KeyInfo element, which wraps a SecurityTokenReference element. The
ecurityTokenReference element has a Reference child
element, whose URI attribute refers to the wsu:Id of
the EncryptedKey that was used to produce the
EncryptedData.
If there are more than one EncryptedData and
EncryptedKey elements in a WSS message, then it is computationally
more expensive to find the EncryptedKey corresponding to a
particular EncryptedData structure in Listing 2 as compared to Listing 3. That's because in Listing 2 you have to look into
the ReferenceList elements of several EncryptedKey
structures until you find the required EncryptedKey structure. On
the other hand, if you have to find the EncryptedData structure
corresponding to a particular EncryptedKey Listing 3, you can read the
URI attribute value of the Reference element and jump
directly to the required EncryptedKey structure.
We will call Listing 3 as an encrypted key token with cross reference.
Now look at Listing 4, which shows the following:
EncryptedData element in Listing 4 contains a KeyName element, which specifies the name of the key used for encryption. Here we are assuming that the recipient of this message can locate the actual key corresponding to this name. Therefore, we are not concerned about how an application will map a key name to the actual key.EncryptedKey element inside the wsse:Security header. Instead we just have a ReferenceList element that refers
to the EncryptedData element inside the SOAP body. This type of
ReferenceList element does not specify the key used for producing
the EncryptedData element. This ReferenceList only
specifies the order in which different actions take place in the header (e.g.
if you encrypt and then sign a portion of a message, the
ds:Signature element will appear before the
ReferenceList element. For details please refer to the discussion
accompanying Listings 7
and 8
in the fourth article of my Web Services Security series). We will call this ReferenceList element a reference list only token.
We will implement the first two types of tokens (encrypted key token and encrypted key token with cross reference) in this column and the third type (reference list only token) in the next column of this series.
Now let's see how to implement and use an encrypted key token. In order to implement and use an encrypted key token, we'll need to do the following:
Token interface we presented in Listing 3 of the first column of this series. The Token interface contains several methods and we'll need to implement each of them in a Java class named EncryptedKeyToken.WSSMessage class we introduced in Listing 2 of the first column of this series.encryptElement() and encryptElementWithXPath() methods of the WSSMessage class.Let's do these steps one by one.
|
Have a look at the EncryptedKeyToken class shown in Listing 5. This class implements the Token interface and is responsible for performing the second, third, and fourth steps of using XSS4J for XML encryption that we described in the second column of this series. The final result of using an encrypted key token is what we have already seen in Listing 2.
Have a look at the EncryptedKeyToken constructor, which takes the following parameters:
keyStoreFileName is the full path name of a Java key store file. The key store file contains the public key of the recipient of the WSS message.keyStorePassword is the string representation of the password that we need in order to access the key store.KeyName is the string representing the name of the recipient's public key that is stored in the key store.wsuKeyID is the wsu:Id of the encrypted key token. The token will author this IDin the token's XML format.wssMessage is the parent WSSMessage object that wraps this token. The constructor first stores the parent WSSMessage object, the key token ID, and the key name in their corresponding fields. It then loads the encryption template in a DOM object. We demonstrated the use of an encryption template in Listing 3 of the second column. Listing 6 shows the encryption template that our EncryptedKeyToken will use. Listing 6 is slightly different from the encryption template we introduced in Listing 3 of the second column. We will explain the difference between the two templates later.
The EncryptedKeyToken constructor then opens the key store file, loads it into an input stream, and loads the input stream into a Java KeyStore object. It then instantiates a KeyStoreKeyInfoResolver object and calls its setAlgorithmFactory() method to set the algorithm factory to be used during XML encryption. Recall that we have covered the details of these steps in the Using XSS4J for XML Encryption section of the second column of this series.
Notice that the EncryptedKeyToken constructor cannot access the key in the key store. That's because we have not supplied the password required to access the key residing inside the Java KeyStore object. The application that instantiates the EncryptedKeyToken object will also call the EncryptedKeyToken.setSecret() method to specify the password to access the key.
The setSecret() method (Listing 5) takes the key access password in byte array form, converts the byte array to a character array, and supplies the character array to the KeyStoreKeyInfoResolver. The KeyStoreKeyInfoResolver will internally fetch the required key.
Now have a look at the encrypt() method in Listing 5. This method authors the encrypted form (an EncryptedData structure) of the plain text element that we want to encrypt. The encrypt() method takes three parameters:
wsuEncryptedElementID: The wsu:Id of the EncryptedData structure that the encrypt() method will author.encryptionAlgo: The algorithm to be sued for encryption.elementWantToEncrypt: The DOM element representation of the XML element that we want to encrypt.The encrypt() method performs the following eight steps (marked with comments in Listing 5):
Step 1: KeyStoreKeyInfoResolver can do both encryption and decryption. So the first step is to set the mode of operation of the key resolver to "encryption."
Step 2: If you look at the encryption template we are using (Listing 6), you will find that there is a ReferenceList child of the EncryptedKey element. This ReferenceList element was not present in the encryption template we introduced in Listing 3 of the second column. We need this ReferenceList element in the EncryptedKey because the final objective of implementing the EncryptedKeyToken class is to author the structure shown in Listing 2 (which contains a ReferenceList element).
Notice from Listing 2 that the DataReference child of the ReferenceList element refers to the wsu:Id of the EncryptedKey that was produced using the EncryptedKey. Therefore, our second step is to add a URI attribute to the DataReference element. The URI attribute refers to the wsu:Id of the EncryptedData that we are about to author.
Step 3: The third step is to author the wsu:Id of the EncryptedData that we will shortly author. At this moment the encryption template looks like as shown in Listing 7.
Step 4: We also need to tell that it is the encryption algorithm that we want to use. For this purpose, our fourth step is to set the value of the Algorithm attribute of the EncryptionMethod element in Listing 7.
Step 5: The EncryptedKey element has its own wsu:Id. So the fifth step is to author the wsu:Id of the EncryptedKey element.
Now our template is all set and looks like what's shown in Listing 8.
Step 6: The next step is to XML encrypt the input DOM element using the procedure we described while discussing step 4 of KeyInfo and encrypt it using the Using XSS4J for XML Encryption section of the second article of this series.
Step 7: Assuming we encrypted the first Parameter element of Listing 1, it will look like Listing 9 after encryption. If you compare Listing 2 and Listing 9, you will find that the EncryptedData element in Listing 9 is not the required form that we were trying to author (see EncryptedData in Listing 2).
The main difference between the EncryptedData elements in Listing 2 and Listing 9 is that the EncryptedData element in Listing 9 contains a KeyInfo element, which in turn contains the EncryptedKey element. In Listing 2, the same EncryptedKey element resides in the SOAP header.
Therefore, the seventh step is to copy the EncryptedKey element and store it as a DOM element object named tokenElement.
Step 8: Now we remove the entire KeyInfo child of the EncryptedData element, so that the EncryptedData element in Listing 10 looks exactly like the EncryptedData in Listing 2.
The EncryptedKeyToken class contains another public method named getXMLString(), which returns the string form of the encrypted key token. Notice that in step 7 above we stored the EncryptedKey in an object named tokenElement. The getXMLString() method simply returns the string form of the tokenElement object.
Now let's see how the WSSMessage class will use the EncryptedKeyToken class to author a WSS message containing an encrypted key token.
WSSMessage ConstructorListing 11 shows the WSSMessage implementation. Have a look at the WSSMessage constructor. The constructor is very simple. It just takes a plain text SOAP message and loads it into a DOM object. For the moment, our WSSMessage class constructor is very humble. It will become more complex in later columns as our WSSMessage class grows in functionality.
WSSMessage ClassNote that the WSSMessage class, as described in the first column of this series, represents the whole WSS message (as opposed to the EncryptedKeyToken class, which represents only a single token). Therefore, the encrypt() method of the EncryptedKeyToken class only handles one type of token, while the encryption methods in the WSSMessage class should be able to handle different types of tokens.
Look at the encryptElement() and encryptElementWithXPath() methods in Listing 11. Each method takes four parameters as already described in the first column. Each of the two methods first locates the element to be encrypted and then calls a method named encrypt(). The encrypt() method is private and performs the following tasks:
encrypt() element simply calls the token.encrypt() method.encrypt() method works according to the eight-step procedure described above and encrypts the plain text element.WSSMessage.encrypt() method calls the getXMLString() method of the token object. The getXMLString() method returns the string representation of the encrypted key token.WSSMessage.encrypt() method places the token inside the Security header, which resides inside the SOAP header.Listing 12 shows the final result when the first Parameter element of Listing 1 is encrypted using an encrypted key token.
In a similar manner the WSSMessage.encrypt() method will handle different types of tokens.
WSSMessage Class in an XML Encryption ApplicationThe final question is how an application will use our WSSMessage and EncryptedKeyToken classes. Have a look at Listing 13, which shows the main() method of a WSSEncryptionSample class. You can see the following steps in the main() method of Listing 13, which demonstrates how easy and simple it is to use our WSS4J API for encryption:
main() method first instantiates a WSSMessage object, passing a plain text SOAP message string to the WSSMessage constructor.EncryptedKeyToken object and then calls its setSecret() method passing the key access password along with the method invocation call.encryptElement() method of the WSSMessage object, passing the token as well as the wsu:Id of the plain text element to be encrypted.This section demonstrates how we can easily enhance our EncryptedKeyToken to implement support for the encrypted key token with cross reference that we introduced in Listing 3. This needs the following steps:
EncryptedKeyTokenWithCrossReference, which extends the EncryptedKeyToken class. We have shown the
EncryptedKeyTokenWithCrossReference class in Listing 14. EncryptedKeyTokenWithCrossReference constructor takes the same
parameters as the EncryptedKeyToken constructor. Since we don't need any additional functionality in the
EncryptedKeyTokenWithCrossReference constructor, the
EncryptedKeyTokenWithCrossReference constructor simply calls the
super's constructor. encrypt() method in the
EncryptedKeyTokenWithCrossReference class needs just one extra
step as compared to the encrypt() method in the
EncryptedKeyToken class. The extra step is to add the
SecurityTokenReference structure (that we described in Listing 3) wrapped inside a
KeyInfo element. Therefore, in the encrypt() method
of the EncryptedKeyTokenWithCrossReference class, we simply call
the super's encrypt() method and then add a
SecurityTokenReference element. We have demonstrated the use of EncryptedKey structure in WSS
messages. Next time, we will start by implementing the reference list token
that we introduced in Listing 3. We will
also discuss the use of XSS4J for XML digital signatures in the next column of
this series.
ResourcesDownload the source code zip of this article. The zip contains the source as well as the compiled form of the code. The zip also contains a readme.txt file that will help you compile and run the code of this article. Read the first and second column of this series, which introduced the WSS4J API. Also check out the first, second, third, and fourth parts of the series of article on Web Services Security from the same author. Check out the official JAXP and JCA/JCE pages at Java.Sun.com. Visit BouncyCastle.org to download the latest
version of their provider. We tested the code of this article with the
Download your copy of XSS4J from IBM alphaWorks web site. Download ICU4J from here. This tutorial at IBM developerWorks will help you learn the basics of cryptography in Java. Read this article at OnJava.com that introduces JAXP. Also check out this page about JAXP. Check out these articles on XML Encryption (Part 1 and Part 2) by the same author. |
XML.com Copyright © 1998-2006 O'Reilly Media, Inc.