Implementing WS-Security with Java and WSS4J

eb services have evolved into a standard means for integrating organizations using differing technologies running on heterogeneous systems and frameworks. A Web service is a business-logic component designed to be accessed across a network using industry-standard protocols and data formats. A Web service exposes a public interface described by a standard industry document format such as a WSDL file. This description document lets external systems understand and interact with the Web service over standard transport protocols such as HTTP, with messages encapsulated using standard message protocols such as SOAP.

Web services produce loosely-coupled systems that clients typically communicate with in a stateless, asynchronous manner, requiring no concern for the underlying protocol or location of the service. Unfortunately, this loosely-coupled, open communication environment is rife with potential security threats, as the next section illustrates.

Web Services Security Threats
Traditional security technologies are not sufficient for Web services security because of the need to secure data and components on a more granular scale. Because Web services use message-based technologies for complex transactions across multiple domains, traditional security processes fall short. A Web-service message can traverse several intermediaries before it reaches its final destination. Therefore, the need for sophisticated message-level security becomes a high priority and is not addressed by existing security technologies.

The following list illustrates some of the specific Web-services security threats:

  • Message Alteration?A scheming entity can attempt to alter the contents of a message, thereby compromising the integrity of the message.
  • Confidentiality?Unauthorized entities can seek to gain access to confidential information within a message.
  • Man-in-the-middle?An attacker can seek to pose as a legitimate SOAP intermediary in order to intercept messages transmitted between two or more parties. The parties, thinking they are communicating with each other, continue their conversation, but with messages that may be altered by the attacker or even originated by the attacker.
  • Identity Spoofing?Unauthorized access using authentication attacks and eavesdropping
  • Content-borne threats?Threats against XML payload elements.
  • Denial of Service (XDoS) attacks
  • Schema Poisoning?This involves manipulating the WS schema to alter the data processed by an application.
  • XML Parameter Tampering?Injection of illegitimate scripts or content into XML parameters.
  • Coercive Parsing?Injection of illegitimate content into the actual XML payload.
  • XML Routing Detours?Redirecting data addressed by an XML path

Given these threats, clearly, a secure solution is imperative.

Introduction to WS-Security
The WS-Security standard specifies extensions to SOAP messaging that provide message-level integrity, confidentiality, and authentication. WS-Security enables collaboration between other Web services security standards and protocols. Because WS-Security does not dictate one specific security technology, the WS-Security specification allows organizations to use heterogeneous security models and encryption technologies, as well as a number of different security tokens.

The WS-Security specification is concerned with three main area of focus:

  1. Security token validation (authentication)
  2. Message integrity (signing)
  3. Message confidentiality (encryption and decryption)

Here’s how WS-Security treats these three areas of focus.

Validating Authentication Claims using Security Tokens
WS-Security uses security tokens to validate authentication assertions made by principals. These assertions are referred to as claims. Claims can be validated by a message recipient or by an authorized third party, such as a certificate authority.

You can use two types of security tokens:

  1. Unsigned security tokens, such as a username/password token
  2. Signed security token, such as x.509 certificates and Kerberos tickets

Preserving Message Integrity using XML Signatures
WS-Security addresses message integrity (preventing unauthorized message content modification) through XML signatures. You can use signatures to:

  • Verify the origin of a message
  • Validate encryption keys
  • Confirm the claims in a security token

Preserving Message Confidentiality using XML Encryption
WS-Security maintains message confidentiality using XML Encryption in association with security tokens to ensure that sensitive parts of a SOAP message remain confidential.

In the rest of this article, you’ll see how to create these security tokens, add XML signatures, and add XML encryption to your SOAP messages so that they meet the WS-Security specifications.

Keystores and the Java Keytool Utility
Because the WS-Security specification depends on the use of encryption keys and certificates, it’s useful to discuss a mechanism to generate and maintain them.

You can use the Java keytool utility, which ships with the JDK, to generate public/private key-pairs and certificates and maintain them in a password-protected keystore so that your Java programs can use them. A keystore is a standard, password-protected repository, also known as PKCS#12, which you can use to store and transport keys and certificates securely.

Creating a Keystore and Key-Pair
The keytool utility can generate a key pair. Typically, you must generate two key-pairs to use one as a certificate/public-key for the other; therefore, execute the keytool with the -genkey option twice, and store each distinct key-pair into a separate keystore.

Here’s how to use the keytool utility to generate a key-pair as a private key.

Author’s Note: Enter the command lines shown below on a single line.

   %JAVA_HOME%inkeytool -genkey -alias privkey        -keystore privkeystore -dname "cn=privkey"        -keypass foobar -storepass foobar

To generate a key-pair to use as a certificate/public-key, use this code (again, enter the entire command on a single line).

   %JAVA_HOME%inkeytool -genkey -alias pubcert       -keystore pubcertkeystore -dname "cn=pubcert"       -keypass foobar -storepass foobar

The preceding commands

  • generate separate key-pairs
  • store the key-pairs in separate keystores
  • specify passwords for the keys and the keystores
  • specify the alias/name for each key-pair
  • specify the common name (sometimes referred to as the distinguished name) by which each key-pair will be known within each keystore.

To examine the contents of a keystore, execute the keytool utility with the -list option. For example, to examine the first (privkeystore) contents created earlier use:

   %JAVA_HOME%inkeytool -list -keystore privkeystore   Enter keystore password:  foobar      Keystore type: jks   Keystore provider: SUN      Your keystore contains 1 entry      privkey, Jul 25, 2005, keyEntry,   Certificate fingerprint (MD5):    A1:FA:99:E2:A7:E8:1A:FB:D8:B7:87:91:D1:0E:9C:F8

Now, look at the pubcert certificate keystore:

   %JAVA_HOME%inkeytool -list -keystore pubcertkeystore   Enter keystore password:  foobar      Keystore type: jks   Keystore provider: SUN      Your keystore contains 1 entry      pubcert, Jul 25, 2005, keyEntry,   Certificate fingerprint (MD5):    99:8F:14:C5:BB:21:86:77:D2:CF:56:DE:98:DD:74:62

To examine a key in detail, you can use the keytool utility to display it to the console in RFC 1421 format using the -rfc option, as follows:

   %JAVA_HOME%inkeytool -export -keystore privkeystore       -alias privkey -storepass foobar --rfc

You’ll see output on the console similar to the following:

   -----BEGIN CERTIFICATE-----   MIIBlTCB/wIEQuWjhTANBgkqhkiG9w0BAQQFADASMRAwDgYDVQQDEwd0ZXN   0a2V5MB4XDTA1MDcyNjAyNDQyMVoXDTA1MTAyNDAyNDQyMVowEjEQMA4GA1   UEAxMHdGVzdGtleTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAz/HFY   xicr+vonubY3rgnJFdl6OsvbinR2L54U7WKHNz2w7w3cOvTMGqop/xQtePx   k3hXIJFs27OBC28Y8jRKYdgGDYMVU5/V0ddlGQUgfU7Xy9jdIPm61ayu3QH   9LcXYSzVfHNeL3HHRcJV3jSwRs1K/vIVZKLNnBRufe2kORK0CAwEAATANBg   kqhkiG9w0BAQQFAAOBgQBWAoAzG5B54dNUt7t3iU98Dre0EI9JkEn8HYiix   oJxs1SmI/vESDbuAJY9EbjlPnvhHrgZL3rtb8twwzHwbLhnxVeV/LRk2C2e   ghkPPEklp3w+UVv5U3dsvoR6LO4z3fTjnc+YbMG0Iss5gkwxJqYy/6qeyYY   3EGoxl8Ehyu/hOw==   -----END CERTIFICATE-----

Self-Signing Certificates
Keys are unusable unless they are signed, but you can use the keytool to self-sign them (for testing purposes only), as follows:

   %JAVA_HOME%inkeytool -selfcert -alias privkey       -keystore privkeystore -keypass foobar -storepass foobar

Now, the certificate can be self-signed, as follows:

   %JAVA_HOME%inkeytool -selfcert -alias pubcert       -keystore pubcertkeystore -keypass foobar       -storepass foobar

Exporting Certificates with the Keytool Utility
After generating and self-signing the keys/certificates and storing them in the keystores, import each public key into the other key’s keystore. This requires two steps: exporting the public key to a certificate file and importing the certificate to the other keystore. To export the public key to a certificate file, use:

   %JAVA_HOME%inkeytool -export -keystore pubcertkeystore       -alias pubcert -storepass foobar -file pubcert

You should see a response that says:

   Certificate stored in file 

You can also use the keytool utility to display the contents of the certificate file using the -printcert option, as follows:

   %JAVA_HOME%inkeytool -printcert -file pubcert

The output will look like:

   Owner: CN=pubcert   Issuer: CN=pubcert   Serial number: 42e5b3c4   Valid from: Mon Jul 25 21:53:40 MDT 2005 until:       Sun Oct 23 21:53:40 MDT 2005   Certificate fingerprints:   MD5:     99:8F:14:C5:BB:21:86:77:D2:CF:56:DE:98:DD:74:62      SHA1: EC:59:92:E9:1F:8A:A6:0A:85:54:EC:76:47:DB:5F:3F:D2:15:78:77

The exported certificate contains the public key and distinguished name given to the certificate (in this case, pubcert).

Importing Certificates into Keystores
To import a public certificate into the keystore of the private key, issue the command:

   %JAVA_HOME%inkeytool -import -alias pubcert      -file pubcert -keystore privkeystore -storepass foobar

The output looks like:

   Owner: CN=pubcert   Issuer: CN=pubcert   Serial number: 42e5b3c4   Valid from: Mon Jul 25 21:53:40 MDT 2005 until: Sun Oct 23 21:53:40 MDT 2005   Certificate fingerprints:   MD5:     99:8F:14:C5:BB:21:86:77:D2:CF:56:DE:98:DD:74:62      SHA1: EC:59:92:E9:1F:8A:A6:0A:85:54:EC:76:47:DB:5F:3F:D2:15:78:77

Answer the following question:

   Trust this certificate? [no]:  yes   Certificate was added to keystore

Now that the certificate has been imported into the private key’s keystore, you can reexamine the contents of the keystore using the keytool utility with the -list option, as follows:

   %JAVA_HOME%inkeytool -list -keystore privkeystore   Enter keystore password:  foobar   

After entering your password you’ll see the following output:

   Keystore type: jks   Keystore provider: SUN      Your keystore contains 2 entries      privkey, Jul 25, 2005, keyEntry,   Certificate fingerprint (MD5):      E7:4A:D9:D7:67:A6:6D:E7:A5:C4:28:22:3D:C5:C4:30   pubcert, Jul 25, 2005, trustedCertEntry,   Certificate fingerprint (MD5):      99:8F:14:C5:BB:21:86:77:D2:CF:56:DE:98:DD:74:62

As the preceding examples illustrated, there are now two entries in the private-key’s keystore. The first, with the alias testkey, is identified as a key entry. The second entry is the certificate from the certificate file.

At this point you have performed sufficient key management tasks to use the private-key keystore to perform WS-Security tasks using the Apache Web Services Security for Java framework.

Using the WSS4J Framework
Apache’s WSS4J is a Java implementation of the OASIS Web Services Security (WS-Security) specification. WSS4J is a framework that you can use to sign and verify SOAP messages with WS-Security information. WSS4J uses the Apache Axis and Apache XML-Security projects and is interoperable with JAX-RPC server/clients and .NET server/clients. WSS4J implements Username Token profile V1.0 and X.509 Token Profile V1.0. WSS4J can generate and process the following SOAP Bindings:

  • XML Security
    • XML Signature
    • XML Encryption
  • Tokens
    • Username Tokens
    • Timestamps
    • SAML Tokens

WSS4J can secure Web services deployed in most Java Web services environments; however, it ships with specific support for the Axis Web services framework. You can use WSS4J in a standalone manner or in tandem with Axis to create and process WS-Security elements within a SOAP envelope. You can obtain the latest release of the WSS4J project by checking out the CVS module, ws-wss4j, using the following CVS path with any CVS client.

 :pserver:[email protected]:/home/cvspublic.

The tools in the next sections demonstrate how to use WSS4J to generate XML that conforms to the latest WS-Security specification.

Initializing the WSS4J Framework
First, you must initialize the WSS4J framework?in this case, to the default values. For example:

   private static final WSSecurityEngine secEngine =      new WSSecurityEngine();

Next, create a crypto provider. The default factory getInstance()method creates a provider according to the class name specified by the system property org.apache.ws.security.crypto.provider. If the provider property is not set, the getInstance() method creates a default class instance, org.apache.ws.security.components.crypto.BouncyCastle.

The provider is initialized to the values specified in the crypto.properties file found in the WSS4J .jar file. As shipped, that file specifies org.apache.ws.security.components.crypto.Merlin as the provider class.

   private static final Crypto crypto =      CryptoFactory.getInstance();

You use the AxisClient as the context engine for messaging operations.

   private AxisClient engine = null;   private MessageContext msgContext = null;      public WSSecuritySample()   {      engine = new AxisClient(new NullProvider());      msgContext = new MessageContext(engine);   }

Creating the Target SOAP Envelope
The following method creates and returns an Axis message from a SOAP envelope string.

   private Message getAxisMessage(String unsignedEnvelope)   {      InputStream inStream =         new ByteArrayInputStream(         unsignedEnvelope.getBytes());      Message axisMessage = new Message(inStream);      axisMessage.setMessageContext(msgContext);      return axisMessage;   }

The SOAP envelope used in this article and passed to the getAxisMessage method shown above is illustrated as follows:

                                                         Hello world!                                          

Signing a SOAP Message
The following method uses the WSSignEnvelope class to sign a SOAP envelope and adds the signature data to the envelope in compliance with WS-Security.

   public Message signSOAPEnvelope(SOAPEnvelope       unsignedEnvelope)  throws Exception   {      WSSignEnvelope signer = new WSSignEnvelope();         String alias = "16c73ab6-b892-458f-abf5-2f875f74882e";      String password = "security";      signer.setUserInfo(alias, password);         Document doc = unsignedEnvelope.getAsDocument();

The “build” method creates the signed SOAP envelope. It takes a SOAP Envelope as a W3C Document and adds a WSS Signature header to it. The signed elements depend on the signature parts specified by the WSBaseMessage.setParts(java.util.Vector parts) method. By default, it signs the SOAP Body element.

The “crypto” parameter is the object that implements access to the keystore and handling of certificates.

WSS4J includes a default implementation, org.apache.ws.security.components.crypto.Merlin.

      Document signedDoc = signer.build(doc, crypto);         // Convert the signed document into a SOAP message.      Message signedSOAPMsg =         (org.apache.axis.Message)AxisUtil.toSOAPMessage(signedDoc);         return signedSOAPMsg;   }

Listing 1 shows a signed SOAP envelope as returned from the preceding method.

Adding Username Tokens to a SOAP Message
Listing 2 shows a WSS4J method that uses the WSEncryptBody class to add username tokens to a SOAP envelope in compliance with WS-Security.

The SOAP envelope contained within the Axis message returned from the method shown in Listing 2 will look similar to Listing 3:

Encrypting SOAP Messages
The following method uses the WSEncryptBody class to encrypt part of a SOAP envelope in compliance with WS-Security.

   public Message encryptSOAPEnvelope(      SOAPEnvelope unsignedEnvelope, Message axisMessage)      throws Exception   {      WSEncryptBody encrypt = new WSEncryptBody();      encrypt.setUserInfo(         "16c73ab6-b892-458f-abf5-2f875f74882e");         // build the encrypted SOAP part      Document doc = unsignedEnvelope.getAsDocument();      Document encryptedDoc = encrypt.build(doc, crypto);         // Convert the document into a SOAP message      Message encryptedMsg =         (Message)AxisUtil.toSOAPMessage(encryptedDoc);         // Retrieve the desired SOAP part      String soapPart = encryptedMsg.getSOAPPartAsString();      ((SOAPPart)axisMessage.getSOAPPart()).         setCurrentMessage(soapPart, SOAPPart.FORM_STRING);         encryptedDoc =         axisMessage.getSOAPEnvelope().getAsDocument();         // Convert the document into a SOAP message      Message encryptedSOAPMsg =         (Message)AxisUtil.toSOAPMessage(encryptedDoc);         return encryptedSOAPMsg;   }

The SOAP envelope contained within the Axis message returned from the preceding method will look like Listing 4.

The “main” Method
Finally, a main method drives the methods defined above to sign, add username tokens to, and encrypt a SOAP envelope.

public static void main(String[] args){   try   {       WSSecuritySample app = new WSSecuritySample();       Message axisMessage = app.getAxisMessage(soapMsg);       SOAPEnvelope unsignedEnvelope =          axisMessage.getSOAPEnvelope();       System.out.println(          "<<< Unsigned and Unencrypted >>>");              XMLUtils.PrettyElementToWriter(          unsignedEnvelope.getAsDOM(),          new PrintWriter(System.out));       Message samlMsg =           app.addUserTokens(unsignedEnvelope);       System.out.println("
<<< User Tokens >>>");       XMLUtils.PrettyElementToWriter(          samlMsg.getSOAPEnvelope().getAsDOM(),          new PrintWriter(System.out));       Message encryptedMsg = app.encryptSOAPEnvelope(          unsignedEnvelope, axisMessage);       System.out.println("
<<< Encrypted >>>");       XMLUtils.PrettyElementToWriter(          encryptedMsg.getSOAPEnvelope().getAsDOM(),          new PrintWriter(System.out));       Message signedMsg =           app.signSOAPEnvelope(unsignedEnvelope);       System.out.println("
<<< Signed >>>");       XMLUtils.PrettyElementToWriter(          signedMsg.getSOAPEnvelope().getAsDOM(),          new PrintWriter(System.out));   }   catch (Exception e)   {       e.printStackTrace();   }}

Although the process may initially seem complex, a method such as the main method shown above simplifies the process considerably, breaking it down neatly into just a few steps: creating a SOAP envelope, and then signing, encrypting, and adding username tokens to it. I urge you to download the sample code for this article and experiment with the process. The WSS4J framework provides the core methods you need to meet the WS-Security specifications.

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: