Part 3 - Web Services Security

Java Development News:

Part 3 - Web Services Security

By Zdenek Svoboda

01 Jan 2002 | TheServerSide.com

This is the third in our series of hands-on tutorials designed to explain how to create and use Web Services. In Part One we introduced some basic concepts, including SOAP and WSDL, and we created and ran a simple Web Service. In Part Two we progressed to handling SOAP complex types, error processing, and dealing with remote references.

In this tutorial we'll introduce and review security and Web Services. This is a controversial subject, with security the most oft-cited concern among developers considering adopting Web Services technologies for the first time. We'll begin by reviewing some basic concepts, then look at common security techniques such as SSL (Secure Socket Layer). We'll then examine the process involved in authentication and authorization, as well as data privacy (if you're already familliar with security concepts, you might want to begin at Section 1.2).

In these series of articles we've used Web Services software tools and code examples that can be downloaded from Systinet's website. Instructions on using these applications and files can be found at the end of this article.


Introduction to Web Services Security

We'll begin by briefly introducing the concepts and archtitecture that underpin Web Service security.


Asymmetric Cryptography - Private & Public Keys

During the Second World War, the US Navy found that one of the best forms of encryption for radio transmissions was to speak in the language of the Najavo Indian, a language that bore no known linguistic connection to any other tongues. This simple technique was never broken, and outperformed more complex code book and computational mechanisms.

Modern cryptography has advanced considerably since then. For the purposes of this discussion, there are two basic cryptographic algorithms: symmetric (or conventional) and asymmetric (also known as public-private key cryptography). For symmetric encryption, the key (or code) that is used to encrypt a file or message is the same key that is also used to decrypt the file or message. For asymmetric encryption, two different keys are used to lock and unlock (encrypt and decrypt) messages and files. The two keys are mathematically linked together but the derivation of one key from another is mathematically unfeasible. An individual's public key is distributed to other users and is used to encrypt messages to the individual. The individual keeps the private key secret and uses it to decrypt messages sent with the public key.

From a computational standpoint, passing messages using symmetric encryption is efficient compared to the more computationally intensive asymmetric method. Asymmetry has advantages when working in large communities because the 'public' key can be freely distributed.


Identity

Identity is the keystone that permits Web services to work together. Today most Web Services work alone, but everyone talks about assembling Web Services to create more powerful and complex business services. Web Service assembly, though, requires that services share information, particularly once we start adding security to our services.

Consider how most Web Services implement security today. Each business that offers a secure Web Service maintains a list of authorized users, and each user authenticates himself using a userid/password challenge. This doesn't make sense for complex services, and this is one reason why we're hearing about major companies like Microsoft announce that Passport will support federated authentication using Kerberos V5.0, and Sun launch the Liberty Alliance Project.

Identities are used during authentication between communicating peers, allowing identification and therefore authorization, auditing, etc. The most common approach today is that each security identity has a name, private key and X.509 certificate. The X.509 certificate contains public characteristics (aka credentials) of a particular identity, including the public key.


Data signing

Data signing is simply proving the data origin. If one encrypts the data using the private key then anyone who is able to correctly decrypt the data can easily prove the data origin: correct decryption means that the data was encrypted by the corresponding private key. As the private key is always kept private and never leaves the possession of the person who generated it, the correct data decryption is proof of their origin.

Unfortunately, asymmetric alghorithms are quite slow, so special hash functions (e.g. MD5 or SHA) are used. These special hash functions first compute the data fingerprint (for example, 16 bytes for MD5 and 20 bytes for SHA) that is later signed. The receiving party computes the fingerprint using the same function, decrypts the signed fingerprint using the public key, and finally compares the computed and decrypted fingerprint. If both fingerprints match then the data's origin is verified.


Trusted certificate

Let's first see how certificates are created. First, key pairs (public and private) are generated. Next, a so-called Certificate Signing Request (CSR) is created. A CSR is simply the data collection that contains all the information that will be included in the certificate (including the public key) and it is signed with the generated private key. Next, the CSR is sent to the so-called Certification Authority that creates the certificate from it and signs the certificate with its private key. By signing the certificate the Certification Authority verifies that the certificate contains valid data. Anyone who trusts this Certificate Authority can use its certificate (more precisely its public key) and verify the signed certificate. The fact that we trust some identity is expressed by storing its certificate in a special, trusted store. The certificate verification can extend to further levels, so certificates can be signed by some identity, that is signed by another identity etc. If any of the certificates in the chain are trusted this implies that any further certificate in the chain is also trusted.


Web Services and Secure API mechanisms

Traditional security technologies used on the Internet often aren't sufficient for Web Services. The major problem is their transport dependence. For example, the most widely-used security technology, SSL (Secure Socket Layer), is tied to the network communication endpoint. More precisely, the identity can be assigned only with the communication endpoint that is usually shared by many Web Services.

Other security technologies, namely GSS-API (Generic Security Service Application Programmers Interface) and security mechanisms based on GSS-API like SPKM (Simple Public Key Mechanism) and Kerberos, are designed specifically for use in loosely-coupled architectures. The GSS-API is independent of both the transportation and security mechanisms (security mechanism independence means that the underlying technologies for cryptography, identity representation, and data signing are fully encapsulated). The most widely used security mechanism in Web Services today is still SSL, but adoption of SPKM and Kerberos is increasing. SPKM is based on the asymmetric cryptography elements explained above, so is well suited to the widely dispersed environment of Web Services; Kerberos uses symmetric cryptography. If you're concerned about security issues, then Web Services products and technologies that support these more advanced security options are the answer.


Security in Action: Authentication, Authorization and Data Privacy

Authentication, Authorization and Data Privacy are the three main elements of a security architecture. Lets go through a simple example that shows these concepts. We will start with the simple implementation of the banking account functionality. Check out the code of AccountImpl Java class.


/*
 * AccountImpl.java
 *
 * Created on December 13, 2001, 9:25 AM
 */

package com.systinet.demos.bank;

// imports of WASP security
import org.idoox.security.AuthResult;
import org.idoox.security.Credentials;
import org.idoox.security.PrincipalAuthenticator;
import org.idoox.security.server.Current;

import org.idoox.webservice.server.Initializable;
import org.idoox.webservice.server.WebServiceContext;

/**
 * Account implementation
 */
public class AccountImpl
    implements Account, Initializable
{
    private double balance = 0;
    private String number = "";

    public AccountImpl()
    {
        this.number = ""+System.currentTimeMillis();
    }

    public void init(WebServiceContext context)
    {
        authenticate();
    }

    public void destroy()
    {
        // do nothing here
    }

    /**
     * Deposits to the account
     * @param amount amount of many to deposit
     * @throws AuthenticationException if authentication fails
     */
    synchronized public void deposit(double amount)
        throws AuthenticationException
    {
        checkAuth();
        this.balance += amount;
    }

    /**
     * Withdraw from the account
     * @param amount amount to withdraw
     * @throws UnsufficientFundsException thrown if account doesn't hava enough funds
     * @throws AuthenticationException if authentication fails
     */
    synchronized public void withdraw(double amount)
        throws UnsufficientFundsException, AuthenticationException
    {
        checkAuth();
        if(amount < this.balance) {
            this.balance = this.balance - amount;
        }
        else {
            throw new UnsufficientFundsException("The withdrawal of " + amount +
                                                 " was requested but the balance is only " +
                                                 this.balance+" .");
        }
    }

    /**
     * Returns the account balance
     * @return the actual balance of the account
     * @throws AuthenticationException if authentication fails
     */
    synchronized public double getBalance()
        throws AuthenticationException
    {
        checkAuth();
        return this.balance;
    }

    /**
     * Sets the account balance
     * @param amount the actual balance of the account
     * @throws AuthenticationException if authentication fails
     */
    synchronized public void setBalance(double amount)
        throws AuthenticationException
    {
        checkAuth();
        this.balance = amount;
    }

    /**
     * Returns the account number
     * @return account number
     * @throws AuthenticationException if authentication fails
     */
    public String getAccountNumber()
        throws AuthenticationException
    {
        checkAuth();
        return this.number;
    }

    /**
     * Sets the account number
     * @param accountNumber account number
     * @throws AuthenticationException if authentication fails
     */
    public void setAccountNumber(String accountNumber)
        throws AuthenticationException
    {
        checkAuth();
        this.number = accountNumber;
    }

    /**
     * Close the account
     * @throws AuthenticationException if authentication fails
     */
    public void close()
        throws AuthenticationException
    {
        checkAuth();
        org.idoox.webservice.server.WebServiceContext context =
            org.idoox.webservice.server.WebServiceContext.getInstance();
        org.idoox.webservice.server.LifeCycleService lc = context.getLifeCycleService();
        lc.disposeServiceInstance(this);
    }

    /**
     * Creates and sets the security identity credentials if they are
     * not alread set
     */
    private synchronized void authenticate()
    {
        Current current = Current.getInstance();
        if (current.getCredentials() == null) {
            PrincipalAuthenticator auth = current.getAuthenticator();
            AuthResult result = auth.authenticate("bank-server", "password".getBytes());
            if (result.resultCode != AuthResult.AUTH_STATUS_SUCCESS) {
                System.err.println("Unable to authenticate");
            }
            current.setCredentials(new Credentials[] { result.creds });
        }
    }

    /**
     * Performs very simple authorization based on the hardcoded
     * identity name, which is able to manipulate the account.
     *
     * @throws AuthenticationException if the client is not authorized
     */
    private void checkAuth()
        throws AuthenticationException
    {
        Current current = Current.getInstance();
        Credentials credentials = current.getReceivedCredentials();
        if(credentials != null) {
            String caller = credentials.getName();
            if(caller == null || !caller.equals("john")) {
                throw new AuthenticationException("Access denied.");
            }
        }
    }

}


Figure 1: Account web service implementation(AccountImpl.java)

All security-related code is in two methods at the end of Figure One, authenticate and checkAuth. The authenticate method opens the local certificate store and retrieves the password-protected public credentials (represented by the identity private key and X.509 certificate) that are later used for the authentication of the service. The client-side code is almost identical. Check out the BankClient Java class source code that performs authentication at the very beginning of its main method.


/*
 * This is a WASP client.
 * BankClient.java
 * Created on December 13, 2001, 10:41 AM
 */
package com.systinet.demos.bank;

import org.idoox.webservice.client.WebServiceLookup;
import org.idoox.wasp.Context;

// imports of WASP security
import org.idoox.security.AuthResult;
import org.idoox.security.Credentials;
import org.idoox.security.PrincipalAuthenticator;
import org.idoox.security.client.Current;

/**
 * Bank client application
 */
public class BankClient
    extends Object
{
    /**
     * Runs the demo on the client-side
     * @param args the command line arguments
     */
    public static void main (String args[])
        throws Exception
    {
        String username = args[0];
        String password = args[1];

        if(username == null)
          username = "john";

        if(password == null)
          password = "password";

        System.out.println("Authenticating user "+username);
        // init the lookup
        WebServiceLookup lookup = (WebServiceLookup)
            Context.getInstance(Context.WEBSERVICE_LOOKUP);

        // obtain and set the crededentials
        Current current = Current.getInstance();
        PrincipalAuthenticator auth = current.getAuthenticator();
        AuthResult result = auth.authenticate(username, password.getBytes());
        if (result.resultCode != AuthResult.AUTH_STATUS_SUCCESS) {
            System.err.println("Unable to authenticate");
            return;
        }
        current.setCredentials(new Credentials[] { result.creds });

        // get the proxy to the Web Service from the lookup
        Account account = (Account)
            lookup.lookup("http://localhost:6060/AccountImpl/", Account.class);
        // now, call the methods on your Web Service interface
        System.out.println("Account #" + account.getAccountNumber() + " created.");
        System.out.println("Putting $10 000 on account #" +
                           account.getAccountNumber() + " .");
        account.deposit(10000);
        System.out.println("Getting $7 000 from account #" +
                           account.getAccountNumber() + " .");
        account.withdraw(7000);
        System.out.println("Account #" + account.getAccountNumber() + " balance is "
                           + account.getBalance());
        account.close();
    }
}

Figure 2: Bank client implementation (BankClient.java)

After the client code calls the service, mutual authentication of the client and server side is performed. Basically, public credentials (certificates) are exchanged, and if the remote parties trust each other's partner certificate, secure communication can take a place.

The checkAuth performs basic authorization. First, the calling side credentials are retrieved from the incoming call. In our example, the name of the client is checked and if it matches 'john', authorization is successful. Otherwise, the authorization fails. Such hard-coded validation is the simplest possible, but unless all your banking partners are called John, it's suitable only for demonstration purposes. In the real world of Johns, Jacks and Janes, technologies like JAAS (Java Authentication and Authorization Service) should be used for propagation of the calling-side identity to the Java code, and for performing authorization checks (usually in a policy file). The JAAS approach allows seamless security integration of Web Services with J2EE application servers that already use this technology for authentication and authorization purposes.

To use the JAAS integration of the WASP Security API, the Credentials Interface has the getSubject() method which obtains the JAAS javax.security.auth.Subject instance.

Data privacy is the last (but far from least important) feature of the Web Services security architecture. Data privacy ensures that received data wasn't modified while in transport, and also takes care of data confidentiality. Usually, data is encrypted using some symmetric cipher (e.g. Tripple DES or RC5) because it is much faster than asymmetric methods. Generally, asymmetric ciphers are only used for safe symmetric key exchange. If you look at the messages exchanged in our simple demo, you should see fully encrypted communication. In some cases, especially in complex message routing scenarios, it make sense to encrypt only specific parts of the message in order to give the communication intermediaries access to message headers. Similarly, the data signing can be also be performed only on specific message elements that require identity verification.


Instructions for running the demo

NOTE: If you haven't already downloaded the software used to create the tutorial examples, then please refer to the installation chapter in Part One. You don't need to download and use the software to understand these articles, but we strongly recommend it. The concepts we introduce and the code we create are generally applicable and relatively independent of the tools used. We assume some knowledge of XML. Unless you have some experience with SOAP and WSDL, we recommend you first read the previous parts of this tutorial series.

When downloading the software you'll also need to download the demo sources. We'll assume that you unpacked this archive into the c:wasp_demo directory. All Java sources mentioned in the tutorial examples can be found in the src subdirectory of the unpacked demo sources archive. They all reside in the com.systinet.demos.bank package. Similarly all scripts used in the demo are located in the bin subdirectory.

NOTE: Please use short names for your WASP Server, Java SDK and demo installation. Otherwise you'll need to modify provided scripts.

ADDITIONAL INSTALLATION STEPS: You will need to install few security add-ins to follow the security demos. It is absolutely necessary to follow the JCE and JAAS installation steps in the security installation document. You can also find this document in the local WASP Advanced installation guide. (doc/htmldata/installation_guide_body.html#security_install relative to the product installation directory).

You'll also need to run the install-security.bat script located in the bin subdirectory of the WASP Advanced installation. This script is interactive. Please specify localhost as your hostname and then accept all default values.

Finally, we will need to modify the env.bat script located in the c:wasp_demobin directory. Please specify the correct values for the WASP_HOME and WASP_DEMO environment variables. To do this you'll need to perform the JCE, JAAS and WASP security installations mentioned above. Then start the web services runtime with the serverstart script in WASP_HOMEbin directory. The next step is the generation of all identities used in demo (you'll notice in the code that the identities bank-server and john are used). All of these identities are generated in the appropriate secure stores. Also, because this demo doesn't use any certification authority, both identities are inserted into the trusted stores on the other communication side. This is the simplest way to make these identities trusted. Next, the client-side identity jack is created: This identity is only added on the client side, so it isn't trusted on the server. Please run the createAccounts script that creates all required identities automatically. Finally, we can use the deploy and run scripts for deploying and running the demo. Please note that you need to specify a WASP administrator username and password, when deploying the demo to the web services runtime (our demo uses the default values). The client application should successfully authenticate the user john, but fail in the case of user jack who isn't trusted on the server (you can also simply specify any other user by the trivial modification of the run script). If you specify any other username, the demo should fail on the client side, stating that the identity can't be authenticated.

NOTE: You may notice that the Account web service is stateful: It remembers the account number and account balance between subsequent calls. If we'd run two client applications in parallel, two separate instances of the Account web service would be created.

The last step is to undeploy the demo using the undeploy script (you'll again need to specify WASP administrator username and password).

NOTE: In future tutorials we will not use security, so we'll need to set WASP server into unsecure mode by running the unsecure script. Please stop the WASP Server before doing this, otherwise the changes made to the configuration files will be lost. You can use the serverstop script located in the WASP_HOMEbin directory.


Review

In this tutorial we became familiar with the basic security architecture of Web Services. For those unfamilar with the concepts, we reviewed symmetric and asymmetric security protocols, identity, SSL and GSS-API. We reviewed the options open to developers using Web Services, and made the case for more advanced systems based on the GSS-API framework. We then demonstrated a simple security system on a Web Service. In the next article, we'll look at the Web Services integration with JNDI and EJBs.