Thursday, October 15, 2009

Client-Side Authentication














































Client-Side Authentication


So far, the connection you have created is
authenticated only on the server side. Although this is fine for most
applications related to, say, Internet shopping, a lot of
business-to-business and corporate applications require that both sides
of the link are authenticated. Additional messages are used in the
handshake leading to a process like the one described in Figure 10-2.






Figure 10-2

As you can see, the SSL protocol enables this by
allowing the client to also send back a certificate chain in the
Certificate message along with the pre-master secret and then sending a
signature, the CertificateVerify message, which can be
validated using the end entity certificate in the client's certificate
chain. The signature is calculated on the data stream that represents
all the messages that have been exchanged starting from the initial
client message. Doing it this way is quite useful, as it does not
require the client to have a public key that can be used for
encryption. Instead, the client can also use a range of digital
signature specific algorithms as well to authenticate itself.


As client-side authentication is an option with SSL,
introducing it requires you to make use of some additional
configuration methods. Normally you will just use the ones on the SSLServerSocket class; however, it is also possible to customize an SSLSocket object in server mode to use client-side authentication. The next section starts with the configuration methods on the SSLServerSocket class.




SSLServerSocket Configuration



Configuring an SSLServerSocket to request client configuration is very straightforward, with two methods now being provided for the purpose; the older setNeedClientAuth() method and a newer method added in JDK 1.4, setWantClientAuth().




SSLServerSocket.set NeedClientAuth()


The setNeedClientAuth() method takes a single boolean parameter. If the parameter has the value true, a server-mode socket created as a result of an accept()
will enforce client-side authentication. If the client is unable to
provide authentication, the connection will be dropped. Calling this
method overrides any previous use of setWantClientAuth(). The method has a corresponding get() method called getNeedClientAuth(), which returns a boolean indicating whether optional client-side authentication will be requested.





SSLServerSocket.set WantClientAuth()


The setWantClientAuth() method takes a single boolean parameter. If the parameter has the value true, a server-mode socket created as a result of an accept()
will initially request client-side authentication. If the client is
unable to provide authentication, the connection will proceed
regardless. Calling this method overrides any previous use of setNeedClientAuth(). The method has a corresponding get() method called getWantClientAuth(), which returns a boolean indicating whether optional client-side authentication will be requested.


The methods setWantClientAuth() and getWantClientAuth() are only available in JDK 1.4 and later.






Server-Mode SSLSocket Configuration


There are also some methods on SSLSocket
that allow you to customize the settings for client-side authentication
for an individual socket being used in server mode. As you would
expect, they are the same as the ones available on the SSLServerSocket class and need to be invoked before the SSLSocket.startHandshake() method has been called.




The setNeedClientAuth() Method


The setNeedClientAuth() method takes a single boolean value. Its meaning is the same as the method with the same name on the SSLServerSocket class, except it only applies to the SSLSocket object it is called on. Likewise, a corresponding get() method, getNeedClientAuth(), is also available.





The setWantClientAuth() Method


The setWantClientAuth() method takes a single boolean value. Its meaning is the same as the method with the same name on the SSLServerSocket class, except it only applies to the SSLSocket object it is called on. Likewise, a corresponding get() method, getWantClientAuth(), is also available, and the get() and set() methods are only available in JDK 1.4 and later.






The SSLContext Class


The javax.net.ssl.SSLContext class is
used as a carrier for the credentials and other information associated
with particular SSL links. You have already used this class indirectly,
because the default factory objects are created using an SSLContext
object, which uses the system properties you were setting to locate the
credentials that were used to identity the server and the trust store
that was used by the client to validate them.


You can also create your own SSLContext objects using the getInstance() factory pattern that you saw in the JCE and the JCA. All the getInstance() methods take a String as a parameter that represents the protocol being requested. In addition to the method that just takes a single String
parameter and uses a default implementation, there are two additional
versions of the method that take the name of the provider required or a
class representing the provider required if you want to specify which
provider should be used to provide the underlying implementation of the
SSLContext object.


Legitimate values for the protocol String parameter are “SSL” or “TLS”, where “SSL” is an alias for “SSLv3” and “TLS” is an alias for “TLSv1”. If the protocol you are requesting is not available, the getInstance() method will throw a NoSuchAlgorithmException. If you use the alternate methods that allow you to specify a provider, the getInstance() method may throw an IllegalArgumentException if the parameter representing the provider is null, and if you use the getInstance() method that accepts a String representing the provider name and the provider cannot be found, a NoSuchProviderException will be thrown.


I'll only cover the commonly used methods on the class
here. If you look at the JavaDoc for JDK 1.5, you'll find that a few
extra methods have been added, such as those allowing you to create
your own javax.net.ssl.SSLEngine objects if you require.
These can be useful if you want to work with the non-blocking I/O
library. I won't go into details on non-blocking I/O here, but you can
find details on using it with SSL in the "JSSE Reference Guide" that
forms part of the Java documentation set.




SSLContext.init()


The init() method is used to initialize the context. It takes three parameters: an array of KeyManager objects, an array of TrustManager objects, and a SecureRandom. If any of the parameter values are null, a default implementation of the required parameter value will be used. You will look at how you create KeyManager and TrustManager objects over the next two examples, but as you have probably guessed, the KeyManager
objects are used to provide credentials needed to identify the local
end of the SSL connection to the peer at the other end, and the TrustManager objects are used to provide the resources required to validate the identity the peer is presenting.


The method will throw a KeyManagementException if the operation fails.





SSLContext.get ClientSessionContext()


The getClientSessionContext() method returns a javax.net.ssl.SSLSessionContext
object associated with the handshake phase of client-side connections.
In the case where the session context is not available, the method
returns null.


If an SSLSessionContext object is
returned, you can use it for finding out and setting the session
timeout, finding out and setting the number of sessions that can be
cached in the context, and finding out the IDs of all the sessions
associated with the context.





SSLContext.getProtocol()


The getProtocol() method returns a String representing the name of the protocol associated with this context. It will be the same name that was specified in the getInstance() call that created this SSLContext object.





SSLContext.get ServerSessionContext()



The getServerSessionContext() method returns an SSLSessionContext
object associated with the handshake phase of server-side connections.
In the case where the session context is not available, the method
returns null.





SSLContext.get ServerSocketFactory()


The getServerSocketFactory() returns an SSLServerSocketFactory that will create SSLSocket objects on accepted connections that will use the protocol, key managers, and trust managers associated with this context.





SSLContext.get SocketFactory()


The getSocketFactory() returns an SSLSocketFactory that will create SSLSocket objects that will use the protocol, key managers, and trust managers associated with this context.






The KeyManagerFactory Class


The javax.net.ssl.KeyManagerFactory class is used to create objects that implement the javax.net.ssl.KeyManager interface. Like other security-related classes, it is created using the getInstance() pattern and follows the usual rules in regards to provider precedence when getInstance()
is called and the provider is not explicitly specified along with the
algorithm requested. The standard algorithm to use with the getInstance() method is “SunX509”.


Aprovider can be specified to getInstance() explicitly as either a String
representing the provider's name or as an object representing the
provider's implementation. If the algorithm requested is not available,
the method throws a NoSuchAlgorithmException. In the event the provider is specified explicitly, the method throws an IllegalArgumentException if the provider parameter is null or if the getInstance() method that takes a provider name is used and the provider cannot be found, the method throws a NoSuchProviderException.


The class has only a few methods on it, and its use as
a factory is straightforward. As you will see in the example following
the method descriptions, use of the factory is a simple two-step
process.




KeyManagerFactory.init()


There are two init() methods on the KeyManagerFactory class. Both methods serve the purpose of providing the KeyManagerFactory with a source for the private keys required for allowing the SSLContext holding KeyManager objects produced by this factory to create an authenticated connection.


The first init() method takes a KeyStore object and a char array representing a key password. If there is more than one private key in the keystore, the KeyManagerFactory object will assume that all keys in the keystore passed in have the same password. The method can throw any of KeyStoreException, NoSuchAlgorithmException, or UnrecoverableKeyException, depending on which problems occur when processing the passed in keystore.


The second init() method takes an object implementing the javax.net.ssl.ManagerFactory Parameters interface. The method will throw an InvalidAlgorithmParameterException if a problem occurs. ManagerFactoryParameters
is a simple marker interface with no methods on it, and how the
parameter object that implements it is interpreted is up to the
underlying provider implementing the KeyManagerFactory object you are using. One use of this interface worth looking at is the KeyStoreBuilderParameters class, which is also in the javax.net.ssl package. The class is an interesting one because it will show you an application of the KeyStore.Builder class that was mentioned in Chapter 8.





KeyManagerFactory.get Algorithm()


The getAlgorithm() method returns a String representing the algorithm name for this KeyManagerFactory object. This will be the same as the algorithm parameter passed to KeyManagerFactory.getInstance().





KeyManagerFactory.get DefaultAlgorithm()


The static getDefaultAlgorithm() returns a String representing the default algorithm name for KeyManagerFactory objects created using the JVM.


The default algorithm can be changed setting the value of the ssl.KeyManagerFactory.algorithm security property in the java.security file. Alternately, it can be changed at runtime by calling the Security.setProperty() method with the property name and the desired algorithm.





KeyManagerFactory.get KeyManagers()


The getKeyManagers() method returns an array of objects implementing the KeyManager interface, one for each type of key material the KeyManagerFactory object was initialized with.


The KeyManager interface is a marker interface with no methods on it, and what actual implementations are returned in the array generated by getKeyManagers() depends on the underlying provider. In the case of SSL based on X.509, the standard JSSE KeyManagerFactory will return objects implementing the javax.net.ssl.X509KeyManager interface or javax.net.ssl.X509ExtendedKeyManager.


Fortunately, you don't have to worry so much about what is returned by the KeyManagerFactory as long as the array contains objects that are compatible with the SSLContext object you are initializing. Having a basic understanding of the return value of the getKeyManagers()
method is enough to allow you to create a client that has client-side
authentication, without the need to make use of Java system properties
as you did with the server in the last example. You will look at how
that is done now.




Try It Out: Introducing Client-Side Authentication






This example introduces client-side authentication and the use of the KeyManagerFactory
class. As before, it is in two parts. Both parts extend from the
classes you created for the last example, so that it isn't necessary to
reproduce the code that supports the "!" protocol that is used to test
the connections.


The first thing you will notice is that most of the changes are on the client class and that they relate to the creation of an SSLContext object, which can be used to create the SSLSocket object that the client uses to connect to the server process.



Here is the source for the new client:


package chapter10;

import java.io.FileInputStream;
import java.security.KeyStore;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;

import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

/**
* SSL Client with client-side authentication.
*/
public class SSLClientWithClientAuthExample extends SSLClientExample
{
/**
* Create an SSL context with a KeyManager providing our identity
*/
static SSLContext createSSLContext() throws Exception
{
// set up a key manager for our local credentials
KeyManagerFactory mgrFact = KeyManagerFactory.getInstance("SunX509");
KeyStore clientStore = KeyStore.getInstance("PKCS12");

clientStore.load(new FileInputStream("client.p12"), Utils.CLIENT_PASSWORD);

mgrFact.init(clientStore, Utils.CLIENT_PASSWORD);

// create a context and set up a socket factory

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(mgrFact.getKeyManagers(), null, null);

return sslContext;
}


public static void main(String[] args) throws Exception
{
SSLContext sslContext = createSSLContext();
SSLSocketFactory fact = sslContext.getSocketFactory();

SSLSocket cSock = (SSLSocket)fact.createSocket(
Utils.HOST, Utils.PORT_NO);

doProtocol(cSock);
}
}

The new server class is very similar to the one in the
previous example. All you need to do is call the configuration method
that specifies that you only want to accept authenticated clients.



Here is the source for the new server:


package chapter10;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;

/**
* Basic SSL Server with client authentication.
*/
public class SSLServerWithClientAuthExample extends SSLServerExample
{
public static void main(
String[] args)
throws Exception
{
SSLServerSocketFactory fact =
(SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
SSLServerSocket sSock =
(SSLServerSocket)fact.createServerSocket(Utils.PORT_NO);

sSock.setNeedClientAuth(true);

SSLSocket sslSock = (SSLSocket)sSock.accept();

doProtocol(sslSock);
}
}

As with the last example, you should run this example
using two windows in the same temporary directory you set up before.
You'll need to do this because this example requires all the keystores
that you created previously. Note that each command should be entered
on one line as before. (Any line breaks you see here are due to the
formatting requirements of this book.)


The first thing to do is to start the new server using the following command:


java -Djavax.net.ssl.keyStore=server.jks
-Djavax.net.ssl.keyStorePassword=serverPassword
-Djavax.net.ssl.trustStore=trustStore.jks chapter10.SSLServerWithClientAuthExample

As you can see, the client is now authenticating itself. You need to provide the server with a trust store.


Now that the server is running, you can run the new client as well:


java -Djavax.net.ssl.trustStore=trustStore.jks
chapter10.SSLClientWithClientAuthExample

In this case, you haven't had to set the javax.net.ssl.keyStore properties as you did for the server, because the client is providing its own KeyManager objects.



You will then see this in the server window:


session started.
session closed.

and the server will exit.


In the client window, you will see


Hello World!

and the client will exit.


















How It Works


In this example, the server end of the link is still largely taking advantage of the default SSLServerSocketFactory
that the JSSE makes available. There are only two changes you had to
make to create a server that expects authenticated clients.


The first change is the addition of the line


              sSock.setNeedClientAuth(true);

which forces the creation of SSLSocket objects that will work only for authenticated clients each time an accept() occurs.


The second change is the setting of the javax.net.ssl.trustStore
parameter on the command line. This needed to be done because,
otherwise, the server would have tried using one of the default trust
store locations and would have been unable to validate the client's
certificate chain.


On the client side, because you are now creating your own SSLContext,
there is considerably more code than before, but in some ways it works
nearly the same way. Most of the changes are restricted to the addition
of the createSSLContext() method. The use of the SSLSocketFactory is still the same; it's just the underlying context that it is created with that is different.


Initially, the createSSLContext() method creates a KeyManagerFactory of the “SunX509” type using the default provider and initializes it using a keystore created from the client.p12 file. Next, an SSLContext object, sslContext, is created for the TLS version 1 protocol using the default provider, and the init() method is called on sslContext, passing it a KeyManager array generated by the KeyManagerFactory object mgrFact and two null parameters.


As the other two parameters passed to sslContext.init() are null, you have created an SSLContext object that uses your provided KeyManager objects and uses the system default TrustManager objects and a system default SecureRandom object. This is the reason why the javax.net.ssl.trustStore system property still needs to be set on the command line(the SSLContext object that is being created still needs to use the default TrustManager object and the default TrustManager
object requires the property to be set. Otherwise, it will be
retrieving the trust anchor used to validate the certificate path from
the wrong keystore.


The answer to avoiding use of the system property is to create your own TrustManager objects. To do this, you need to use the TrustManagerFactory class.






The TrustManagerFactory Class



The javax.net.ssl.TrustManagerFactory class is used to create objects that implement the javax.net.ssl.TrustManager interface. Like other security-related classes, it is created using the getInstance() pattern and follows the usual rules with regard to provider precedence when getInstance()
is called, and the provider is not explicitly specified along with the
algorithm requested. The standard algorithms to use with the getInstance() method are “SunX509” or “SunPKIX”.


Aprovider can be specified to getInstance() explicitly as either a String
representing the provider's name or an object representing the
provider's implementation. If the algorithm requested is not available,
the method will throw a NoSuchAlgorithmException. In the event the provider is specified explicitly, the method will throw an IllegalArgumentException. If the provider parameter is null or if the getInstance() method that takes a provider name is used and the provider cannot be found, the method will throw a NoSuchProviderException.


The class has only a few methods on it, and as you will
see in the example following the method descriptions, the use of the
factory object is the same as for the KeyManagerFactory.




TrustManagerFactory.init()


There are two init() methods on the TrustManagerFactory class. Both methods serve the purpose of providing the TrustManagerFactory with a source for the trust anchors required for allowing the SSLContext holding TrustManager objects produced by this factory to create and authenticate the entity at the other end of the connection it is attached to.


The first init() method simply takes a KeyStore
object and uses any trusted certificates stored in it as trust anchors
that it will recognize when the other party in the connection presents
their credentials. The method will throw a KeyStoreException if there is a problem extracting the certificates from the passed-in keystore.


The second init() method takes an object implementing the ManagerFactoryParameters interface. As mentioned earlier, this is a simple marker interface, but in the case of the TrustManagerFactory class, there is a parameters object defined called CertPathTrustManagerParameters that is also in the javax.net.ssl package. The CertPathTrustManagerParameters class allows you to pass CertPathParameters for use by the TrustManager that will be created by the TrustManagerFactory
object. This can be quite useful if you want to rely on something other
then the default certificate path validation performed by the TrustManager either because you want to use OCSP or your certificates are carrying locally defined extensions, which are marked as critical.





TrustManagerFactory.get Algorithm()


The getAlgorithm() method returns a String representing the algorithm name for this TrustManagerFactory object. This will be the same as the algorithm parameter passed to TrustManagerFactory.getInstance().





TrustManagerFactory.get DefaultAlgorithm()


The static getDefaultAlgorithm() returns a String representing the default algorithm name for TrustManagerFactory objects created using the JVM.



The default algorithm can be changed by setting the value of the ssl.TrustManagerFactory.algorithm security property in the java.security file. Alternately, it can be changed at runtime by calling the Security.setProperty() method with the property name and the desired algorithm.





TrustManagerFactory.get TrustManagers()


The getTrustManagers() method returns an array of objects implementing the TrustManager interface, one for each type of trust information the TrustManagerFactory object was initialized with.


The TrustManager interface is a marker interface with no methods on it, and what actual implementations are returned in the array generated by getTrustManagers() depends on the underlying provider. In the case of SSL based on X.509, the standard JSSE TrustManagerFactory will return objects implementing the javax.net.ssl.X509TrustManager interface. As you will see in the example that follows, the situation with TrustManager objects is the same as with KeyManager objects—the important thing about the return value from the getTrustManagers() method is that it is compatible with the SSLContext object being used.




Try It Out: Using the TrustManagerFactory






This example includes the use of a TrustManagerFactory object in the initialization of the SSLContext object. As you can see from the code, all the functional changes are in the createSSLContext() method.


package chapter10;

import java.io.FileInputStream;
import java.security.KeyStore;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

/**
* SSL Client with client-side authentication.
*/
public class SSLClientWithClientAuthTrustExample
extends SSLClientExample
{
/**
* Create an SSL context with both identity and trust store
*/
static SSLContext createSSLContext()
throws Exception
{
// set up a key manager for our local credentials
KeyManagerFactory mgrFact = KeyManagerFactory.getInstance("SunX509");
KeyStore clientStore = KeyStore.getInstance("PKCS12");

clientStore.load(
new FileInputStream("client.p12"), Utils.CLIENT_PASSWORD);

mgrFact.init(clientStore, Utils.CLIENT_PASSWORD);

// set up a trust manager so we can recognize the server
TrustManagerFactory trustFact = TrustManagerFactory.getInstance("SunX509");
KeyStore trustStore = KeyStore.getInstance("JKS");

trustStore.load(
new FileInputStream("trustStore.jks"), Utils.TRUST_STORE_PASSWORD);

trustFact.init(trustStore);


// create a context and set up a socket factory
SSLContext sslContext = SSLContext.getInstance("TLS");

sslContext.init(
mgrFact.getKeyManagers(), trustFact.getTrustManagers(), null);


return sslContext;
}

public static void main(String[] args) throws Exception
{
SSLContext sslContext = createSSLContext();
SSLSocketFactory fact = sslContext.getSocketFactory();
SSLSocket cSock = (SSLSocket)fact.createSocket(
Utils.HOST, Utils.PORT_NO);
doProtocol(cSock);
}
}

As with the last example, you should run this example
using two windows in the same temporary directory you set up before.
Once again, each command should be entered on one line; any line breaks
are due to the formatting requirements of this book.


The first thing to do is to start the server as before, using the following command:


java -Djavax.net.ssl.keyStore=server.jks
-Djavax.net.ssl.keyStorePassword=serverPassword
-Djavax.net.ssl.trustStore=trustStore.jks chapter10.SSLServerWithClientAuthExample

This is the same as before, which should not be any surprise, because you have only needed to change the client.


Next, you should run the client in the other window using


java chapter10.SSLClientWithClientAuthTrustExample

Because the SSLContext is now initialized with a TrustManager object as well as a KeyManager object, you no longer have to set the JVM properties on the command line.


You will then see this in the server window:


session started.
session closed.


and the server will exit.


In the client window, you will see


Hello World!

and the client will exit.


This indicates that the client has successfully connected, identified itself, and validated the server.


















How It Works


As you can see from the highlighted code in createSSLContext(), creating and using the TrustManagerFactory object is virtually the same as for the KeyManager. The main difference is that there is no need to provide a password to the init() method, because only trusted certificate entries are extracted from the KeyStore parameter passed in.


The introduction of the TrustManagerFactory object on the client side now means that the only system default the SSLContext is relying on is the SecureRandom
implementation it is using. This frees you from having to set any
system properties on the client side to get the client to run, because
the SSLContext now has all the information it needs to
provide identification information for its end of the link and to
identify other entities connecting to it.


The SSLContext provides an environment in
which an SSL connection can be established, but when a connection is in
use, it doesn't provide any information as to who connected or what
cipher suite is being used. This information is associated with the SSL
session.







































No comments:

Post a Comment