Menu

Web Services Security for Java

October 28, 2003

Bilal Siddiqui

My new WebServices.XML.com column, which focuses on web services security, will demonstrate practical aspects of using various security standards for web services along with specific server side technologies and programming languages. In the first few articles of the column, I will demonstrate the use of web services security (WSS) in Java applications, and I will outline what is required for their implementation. This first column presents a simplified high-level API that offers Java programmers an easy interface to produce and consume WSS messages.

In this column I discuss security tokens, token references, XML encryption (for convenience I use the namespace prefix xenc), and XML digital signatures (prefix ds) that I have already covered in my recent four part series (Part 1, Part 2, Part 3, and Part 4).

WSS Use Cases: Client Side

A good place to start is a review of various web services security authoring use cases:

  1. A client application wants to add a security token to a SOAP message for authentication. I discussed several types of security tokens (certificate, username, SAML assertion, etc.) in the second, third, and fourth parts of my Web Services Security series.

    The client authors the complete SOAP Body, passes the SOAP Envelope along with a security token to WSS4J, and asks WSS4J to attach the security token to the SOAP message.

    The client application does not have the capability of generating the WSS structure for the token. So in order to author the token the client application will rely on WSS4J. For example, if the token is an X509 certificate, the client application will simply pass the X509 certificate byte array to WSS4J. WSS4J will author the complete WSS compliant XML structure of the security token, wrap the security token inside a WSS header, and return the completed WSS message back to the client application.

  2. A client application wants to sign a portion of a SOAP message using a private cryptographic key associated with an X509 certificate. The client application has already added the certificate to the SOAP message inside a WSS header as a security token. The client application asks WSS4J to sign a particular portion of the WSS message using the private key associated with the certificate. There are two ways of identifying the particular portion that needs to be signed: either a fragment identifier or an XPath expression.

    If you want to use fragment identifiers, the Web Services Utility (wsu) namespace can help you. wsu is a general purpose namespace (http://schemas.xmlsoap.org/ws/2003/06/utility) which defines utility data structures (such as identifiers, timestamps, etc.) for use by other web services specifications. One of the attributes defined by the wsu namespace is wsu:Id, which is used to hold identifiers for different element nodes.

    WSS clients can use wsu:Id attributes as fragment identifiers by adding a wsu:Id attribute value to the SOAP message element that it wants to sign. For example, in Listing 1 the wsu:Id attribute value of the SOAP message body is "MyMessageBody". So if the client wants to sign the SOAP message body of Listing 1, it will simply ask WSS4J to sign the element with wsu:Id attribute value "MyMessageBody".

    Instead of using the wsu:Id attribute, WSS clients can also use XPath to identify a particular element node of a WSS message. In this case, the client will author an XPath expression to identify the element to be signed. Refer to Resources to learn how to write an XPath expression to identify a particular XML element.

    Whether the client uses wsu:Id or an XPath expression to identify the element to be singed, WSS4J will produce the signature and return the signed WSS message to the client application.

  3. A client application wants to encrypt a particular portion of a SOAP message. The client application first adds the certificate of the intended recipient of the message as a WSS token. It then provides the fragment identifier or an XPath expression to WSS4J and asks the application to produce XML encrypted version of the message. WSS4J produces the encrypted portion of the message and returns the completed message back to the requesting client application.

    The client application can use these use cases in any order and any number of times. For example, a client application can first add a security token to a SOAP message, then sign a portion of the message, then encrypt a part of a message, and so on. Listing 1 is an example WSS message that contains two tokens, a signature, and an encrypted data structure.

Server Side

These three use cases represent the client or producer usage model of WSS4J. Now let's consider the server or consumer usage model.

  1. A server application has received a WSS message with a number of security tokens. The XML firewall needs to extract a list of all tokens from the WSS message. So it asks WSS4J to extract the tokens from the WSS message. WSS4J extracts all the tokens and returns them to the requesting server application.

  2. The server will need to provide a secret key to WSS4J for decryption of encrypted portions of a WSS message. If the author of the WSS message used a public key to encrypt a portion of the message, the corresponding private key is required to decrypt the encrypted portion. Similarly, if the client signed a portion of the message using a session key inside a Kerberos ticket, the server will need a password to decrypt the ticket and extract the session key, without which the server cannot verify the signature (refer to Resources for more details on Kerberos). Therefore, the server may provide a secret to WSS4J corresponding to each token in a WSS message.

    Notice that the server may not know all the secrets associated with all the authentication tokens in a WSS message. That's because a WSS message may be targeted for more than one server. The server only knows the secrets associated with tokens targeted for itself.

    For example, if A receives a WSS message with a Kerberos ticket authentication token targeted for B, A will not be able to extract the session key and use it to verify signatures or decrypt encrypted portions.

  3. The server may also ask the WSS4J to provide a list of all XMLDS signatures in the WSS message. After WSS4J has provided a list of all signatures, the server application can ask WSS4J to process any individual signature.

  4. Similarly, the server may also ask WSS4J to provide a list of all encrypted elements in a WSS message. After WSS4J has provided a list of all encrypted elements, the server application can ask WSS4J to decrypt any individual encrypted element.

The WSS4J API

This section describes a programmatic interface (the WSS4J API) that exposes methods to implement the use case scenarios that we discussed in the previous section. In this column, we will provide a high level view of the WSS4J API. Later columns will incrementally implement the low level details of the API.

Look at Listing 2, which shows a class named WSSMessage, which is the main class in our WSS4J API. This class represents both the client and server side functionality by implementing the following methods:

The WSSMessage() constructor

This method takes a string named soapMessage as a parameter. The soapMessage string represents a SOAP message. The constructor will load the soapMessage string into a DOM document object, thus becoming ready for WSS authoring and processing.

The SOAP message may or may not already contain a WSS security header. If the SOAP message contains a WSS security header, the WSSMessage constructor will parse the WSS message to perform the following functions:

  1. Load all WSS tokens, token references, xenc:EncryptedKey, and ds:KeyInfo elements present in the WSS security header into a list of Token objects. Token is an interface (shown in Listing 3) which represents abstract functionality of a security token as well as xenc:EncryptedKey and ds:KeyInfo elements.

    WSS allows a very flexible and extensible mechanism for defining and using many types of tokens, where each type of token will have its own functionality. That's why we have defined Token as an abstract Java interface and not as a Java class.

    We will later implement the Token interface separately for each type of token and explain the functionality of each Token implementation. For example, the Token interface of Listing 3 contains a method named setSecret(), which takes a byte array and treats the byte array as a secret. In case of an X509 certificate token, the secret will represent the private key associated with the certificate. While in case of a Kerberos service ticket, the recipient's password is the secret that will be used to decrypt the ticket and fetch the session key.

  2. Load all the ds:Signature elements into a list of Signature objects. We will provide details of the Signature class in later columns of this series. For now just note that a Signature object represents a single ds:Signature element and exposes methods to verify the signature it represents. Each Signature object also needs a Token instance associated with it. So the WSSMessage constructor will detect which Token object corresponds to each of the Signature objects and store a reference of the Token instance in the Signature object.

  3. Load all the xenc:EncryptedData elements into a list of xenc:EncryptedData objects. We will discuss the details of the EncryptedData class in a later column. For now just note that an EncryptedData object represents a single xenc:EncryptedData element and exposes methods to decrypt the encrypted portion. Each EncryptedData object also needs a Token associated with it. So the WSSMessage constructor will map Token objects corresponding to each of the EncryptedData objects and store a reference of the Token instance in the EncryptedData object.

The addToken() method

This method takes a Token object as a parameter and adds the token to the WSS message. Every new token is prepended to the existing header, which means it is added as the first child element of the WSS security header.

The addID() method

This is a helper method that takes two string type parameters. The first parameter (XPathExpression) is an XPath filter that specifies an element of the WSS message loaded into the WSSMessage object. The second parameter (wsuId) specifies an identifier for the element. This method adds a wsu:Id attribute with a value equal to the value of the wsuId parameter to the element that the XPathExpresession parameter specifies.

Note if you sign a message first, and then insert any attribute after producing the signature, you will break your signature. Applications should make sure to insert a wsu:Id to an element before signing it.

The encryptElement() method

This method is used to encrypt a portion of the WSS message already loaded into the WSSMessage object. The encrypt method takes four parameters:

  1. The first parameter (wsuElementID) is a wsu:Id for the element to be encrypted.
  2. The second parameter (token) is the Token object (the interface of Listing 3) to be used for encryption.
  3. The third parameter (wsuEncryptedElementID) is the identifier that the encrypt() method will insert into the encrypted element for its identification.
  4. The fourth parameter (encryptionAlgo) identifies the encryption algorithm to be used for encryption.

When a client application calls this message, the method will look for an element in the WSS message whose wsu:Id attribute value matches with the wsuElementID string. It will then encrypt the element to produce the encrypted data structure according to XML encryption syntax.

The encryptElementWithXPath() method

This method is the same as the encryptElement() method described above, except that the element to be encrypted is identified using an XPath expression instead of a wsu:Id.

The sign() method

This method takes six parameters:

  1. The first parameter (wsuElementID) is a WSU identifier for the element to be signed.
  2. The second parameter (token) identifies the Token instance to be used to produce the signature (e.g. the certificate to be used to produce the signature).
  3. The third parameter (wsuSignatureID) is the identifier that the sign() method will include in the ds:Signature element that it is going to produce.
  4. The fourth parameter (digestAlgo) identifies the algorithm to be used in calculating the digest value of the data to be signed.
  5. The fifth parameter (signatureAlgo) specifies the signature algorithm to be used.
  6. The sixth parameter (canonicalizationAlgo) specifies the canonicalization algorithm to be used.

This method signs a particular element in the WSS message to generate a ds:Signature element, which is inserted in the WSS security header.

The signWithXPath() method

This method is the same as the sign() method described above, except that the element to be signed is identified using an XPath expression instead of a wsu:Id.

The getAllTokens() method

This method returns a list of all tokens (an array of objects implementing the Token interface of Listing 3) associated with this WSS message. The WSS4J API takes all occurrences of keys -- public or encrypted symmetric keys -- certificates, Kerberos tickets, and so on, in a WSS message as tokens, whether or not they are used as a WSS token. For example, in Listing 1, the first token (whose wsu:Id is "myCertificate") is a WSS token. On the other hand, the symmetric key (whose wsu:Id is "mySymmetricKey") is not a WSS token. Both these are taken as Tokens in WSS4J API. As already explained, each type of token will implement the Token interface of Listing 3 to expose its own processing logic.

The getAllSignatures() method

This method returns a list of all Signature objects associated with the WSS message. As already mentioned, a Signature object represents a ds:Signature element and exposes methods to process and verify the signature.

The getAllEncryptedData() method

This method is similar to the getAllSignatures() method and returns a list of EncryptedData objects associated with the WSS message. As already mentioned, every EncryptedData object handles the functionality related to a particular occurrence of an xenc:EncryptedData element.

WSS4J Components

You have seen a high level view of the WSS4J API, which sets the scene for us to look into WSS4J implementation details. You will need the following Java-based components to implement the WSS4J API:

  1. XML Security Suite by Apache or IBM
  2. Java Authentication and Authorization Services (JAAS)
  3. Java API for XML Processing (JAXP)
  4. Java Cryptographic Architecture (JCA) and Java Cryptographic Extension (JCE), which are part of the Java Development Kit (JDK) version 1.4
  5. Apache Axis SOAP server

The next column in this series will explain the role of each of these components in implementing the client and server side features of WSS.

Resources