views:

999

answers:

4

Currently, I've been successful implementing Mutual Authentication security so long as the client accesses the website using a web browser, because browsers take care of all the certificate exchange for you. Now I need to create a secure interface with which users can access web services over HTTPS, using the mutual authentication required by the server.

First off, are there any resources anyone knows of that can help me with this? I've looked for quite some time and found nothing. Any other tips anyone can give me on how to go about this?

Secondly, I think my biggest roadblock is my lack of understanding of how to handle certificates. How do I negotiate accepting the server's key and presenting my own key to the server? This is in Java.

A: 

A simple recipe is given in this blog entry.

But I think the real answer may depend on what Java APIs you are using to implement your client-side HTTP interactions. For example, it looks like you would do things a bit differently using JAX-RPC.

Stephen C
A: 

If the web service library uses the standard java.net.URL class as an HTTP client, you can set some system properties and the two-way authentication will be handled by the built-in HTTPS support.

The necessary properties are:

  • javax.net.ssl.trustStore: Contains root CA certificates
  • javax.net.ssl.keyStore: Contains client certificate and private key
  • javax.net.ssl.keyStorePassword: The password protecting the client's private key

These settings become the defaults for all SSL connections by the process. If you want finer control, you have to set up your own SSLContext. Whether that's possible with your webservice runtime depends on which runtime you've chosen.

erickson
+1  A: 

For mutual authentication with SSL (aka two-way SSL) outside a browser, you'll need... Well, actually, let's see what you need for one-way SSL first:

  1. A server keystore
  2. A client truststore

The server keystore contains the server's (possibly self-signed) certificate and private key. This store is used by the server to sign messages and to return credentials to the client.

The client truststore contains the server's (self-signed) certificate (extracted from the server keystore into a stand-alone certificate, without the server private key). This is required if the certificate is not signed by a trusted CA for which you already have a certificate in the truststore bundled with the JRE. This step allows to create a chain of trust.

With this, you can implement one-way SSL (the traditional use case).

To implement two-way SSL, you need to make this setup "symmetric" so we'll need to add:

  1. A client keystore
  2. A server truststore

The client keystore contains the client's (possibly self-signed) certificate and private key. This store is used by the client for the same purpose than the server keystore i.e. to send client credentials to the server during the TLS mutual authentication handshake.

The server truststore contains the clients (self-signed) standalone certificates (extracted from the clients keystore into stand-alone certificates, without the clients private key). This is required for the exact same reasons as previously mentioned.

Some resources to help you to generate all this stuff and to implement the final solutions:

Pascal Thivent
Although you've explained the mutual authentication concept pretty well, the links aren't very helpful at all. Java web service security has changed a fair bit since 2006! :)
Catchwa
A: 

I spent a long time on this but I finally found an example that actually works. It's Glassfish and Netbeans-based but I guess you could get it working in other environments (e.g. Eclipse and Tomcat) if you played around with it.

http://java.sun.com/webservices/reference/tutorials/wsit/doc/WSIT_Security9.html#wp162511

The problem I've found though is when you want to use your own certificates, not the ones that come pre-installed with glassfish.

Note: I am not a security expert. Don't deploy this to a production environment!

To do this I'm using NetBeans 6.9, JDK 1.6, GlassFish 3.0.1 and OpenSSL v1.0 (I'm using the unofficial Win32 binaries)

# Create the CA
mkdir ca server client
cd ca
openssl req -new -x509 -days 3650 -extensions v3_ca -keyout ca.key -out ca.pem
echo 02 > serial.txt
cd ..

# Creating the Server Keystore

openssl req -days 3650 -newkey rsa:1024 -keyout server/server.key -out server/server.req
openssl x509 -extensions usr_cert -extfile C:\testbed\OpenSSL-Win32\bin\openssl.cfg -CA ca/ca.pem -CAkey ca/ca.key -CAserial ca/serial.txt -req -in server/server.req -out server/server.crt
openssl pkcs12 -export -inkey server/server.key -in server/server.crt -out server/server.p12 -name server
keytool -importkeystore -destkeystore server/server.jks -deststoretype jks -srckeystore server/server.p12 -srcstoretype pkcs12
keytool -exportcert -alias server -keystore server/server.jks -file server/server.cer

# Create the Client Keystore

openssl req -days 3650 -newkey rsa:1024 -keyout client/client1.key -out client/client1.req
openssl x509 -extensions usr_cert -extfile C:\testbed\OpenSSL-Win32\bin\openssl.cfg -CA ca/ca.pem -CAkey ca/ca.key -CAserial ca/serial.txt -req -in client/client1.req -out client/client1.crt
openssl pkcs12 -export -inkey client/client1.key -in client/client1.crt -out client/client1.p12 -name client1
keytool -importkeystore -destkeystore client/client1.jks -deststoretype jks -srckeystore client/client1.p12 -srcstoretype pkcs12
keytool -exportcert -alias client1 -keystore client/client1.jks -file client/client1.cer

# Import public keys and certificates into each others keystores

keytool -import -noprompt -trustcacerts -alias client1 -file client/client1.cer -keystore server/server.jks
keytool -import -noprompt -trustcacerts -alias server -file server/server.cer -keystore client/client1.jks
keytool -import -noprompt -trustcacerts -alias my_ca -file ca/ca.pem -keystore server/server.jks
keytool -import -noprompt -trustcacerts -alias my_ca -file ca/ca.pem -keystore client/client1.jks
keytool -import -noprompt -trustcacerts -alias my_ca -file ca/ca.pem -keystore "C:\Program Files\glassfish-3.0.1\glassfish\domains\domain1\config\cacerts.jks"
keytool -import -noprompt -trustcacerts -alias my_ca -file ca/ca.pem -keystore "C:\Program Files\Java\jdk1.6\jre\lib\security\cacerts"
move "C:\Program Files\glassfish-3.0.1\glassfish\domains\domain1\config\keystore.jks" "C:\Program Files\glassfish-3.0.1\glassfish\domains\domain1\config\keystore.jks.backup"
copy server\server.jks "C:\Program Files\glassfish-3.0.1\glassfish\domains\domain1\config\keystore.jks"

In the GlassFish admin console, enable Security on your http-listener, tick the SSL3, TLS and Client Authentication boxes, set the Certificate NickName to server, the Key Store to config\keystore.jks, the Trust Store to config\keystore.jks, the Trust Algorithm to PKIX and leave the Max Certificate Length at 5.

In NetBeans, create a new Web Application project. Within that, create a new Web Service.

My Web Service code looked like this:

@WebService()
public class ListProducts {

  @Resource WebServiceContext context;

  @WebMethod(operationName = "listProducts")
  public String listProducts() {
    return context.getUserPrincipal().toString();
  }

}

Right click on the Web Service and select Edit Web Service Attributes. Tick the Secure Service box and select Mutual Certificates Security as the Security Mechanism. Click on the Configure... button and tick the Encrypt Signature box. Now untick the Use Development Defaults box and then click the Keystore button. Set the location of your server.jks keystore and select the server alias. Do the same for the Truststore configuration (although you don't have to select an alias here).

Import the client1.p12 client certificate into your browser. Deploy your Web Service to Glassfish. Open up your web service in a browser and browse to the deployed WSDL via HTTPS. Download the WSDL and any other schemas. Rename any referenced schemas to local copies so that when you use WSDL2Java NetBeans won't use any remote resources. (This paragraph is because you've restricted your WSDL to clients with an approved certificate but NetBeans can't fetch it remotely because it doesn't have access to the certificate in question).

Create a new Java Project. Create a new Web Service Client. When prompted, point NetBeans to your saved WSDL file. Import the METRO2.0 library files (C:\Program Files\Netbeans 6.9\enterprise\modules\ext\metr\webservices-*.jar). My code looked like this:

public static void main(String[] args) {
  System.getProperties().put("javax.net.ssl.keyStore", "C:\\NetBeansProjects\\security-04\\ssl\\client\\client1.jks");
  System.getProperties().put("javax.net.ssl.keyStorePassword", "changeit");
  System.getProperties().put("javax.net.ssl.trustStore", "C:\\NetBeansProjects\\security-04\\ssl\\client\\client1.jks");
  System.getProperties().put("javax.net.ssl.trustStorePassword", "changeit");
  System.out.println(new ListProductsService().getListProductsPort().listProducts());
}

Copy webservices-api.jar into your Java\jdk1.6\jre\lib\endorsed directory. Right-click on the Web Service reference and select Edit Web Service Attributes. Set the keystore location to client1.jks and set the alias to client1. Set the truststore location to client1.jks and set the alias to server.

Hopefully you can now run your client and you should see output like so: [email protected], CN=Bob Smith, OU=Something, O=SomethingElse, L=AnyTown, ST=AnyState, C=US

Catchwa