Java Secure Socket Extension (JSSE)


Protecting the integrity and confidentiality of data exchanged in network communications is one of the key security challenges of network security. During communication, the potential vulnerability is that the data exchanged can be accessed or modified by someone with a malicious intent or who is not an intended client recipient. Secure Socket Layer (SSL) and Transport Layer Security (TLS) are application-independent protocols developed by IETF that provide critical security features for end-to-end application communication by protecting the privacy and integrity of exchanged data. They establish authenticity, trust, and reliability between the communicating partners. SSL/TLS operates on top of the TCP/IP stack, which secures communication through features like data encryption, server authentication, message integrity, and optional client authentication. For data encryption, SSL uses both public-key and secret-key cryptography. It uses secret-key cryptography to bulk-encrypt the data exchanged between two applications.

JSSE enables end-to-end communication security for client/server-based network communications by providing a standardized API framework and mechanisms for client-server communications. JSSE provides support for SSL and TLS protocols and includes functionalities related to data encryption, message integrity, and peer authentication.

Figure 4-4 illustrates JSSE-based secure communication using the SSL/TLS protocols.

Figure 4-4. Secure communication using SSL/TLS protocols


With JSSE, it is possible to develop client and server applications that use secure transport protocols, which include:

  • Secure HTTP (HTTP over SSL)

  • Secure Shell (Telnet over SSL)

  • Secure SMTP (SMTP over SSL)

  • IPSEC (Secure IP)

  • Secure RMI or RMI/IIOP (RMI over SSL)

Like other security packages, JSSE also features a provider architecture and service-provider interface that enables different JSSE-compliant providers to be plugged into the J2SE environment.

JSSE Provider (SunJSSE)

The J2SE (from J2SE 1.4 and later versions) includes a full-featured JSSE provider named SunJSSE which is pre-installed and pre-configured with JCA. Starting with the release of J2SE 5.0 and later, the SunJSSE provider uses the SunJCE implementation for all its cryptographic needs. You can use other vendor JCA/JCE providers by statically registering them before the SunJCE provider using the java.security properties file located at <java-home>/jre/lib/security/java.security. JSSE providers can also be registered programmatically using the addProvider or insertProviderAt method in the java.security.Security class.

The SunJSSE provider facilitates the Java platform with the following services:

  • Implementation of SSL 3.0 and TLS 1.0 protocols.

  • Implementation of SSL/TLS state machine (SSLEngine), which allows processing of data in the buffer to produce SSL/TLS encoded data (J2SE 5.0 and later).

  • Implementation of key factory and key generators to support RSA algorithms.

  • Implementation of the most common SSL and TLS Cipher suites to support authentication, key agreement, encryption, and integrity protection.

  • X.509-based key manager for managing keys of supporting JCA KeyStore.

  • X.509-based trust manager, implementing support for verifying and validating certificate chains.

  • Support for Kerberos Cipher suites if the underlying OS provides it (J2SE 5.0 and later).

  • Support for hardware acceleration and smart-card tokens using JCE PKCS#11 provider (J2SE 5.0 and later).

Let's take a closer look at the JSSE API mechanisms, core classes, interfaces, and the programming model.

JSSE Classes and Interfaces

The JSSE API framework exists as part of the following packages:

  • javax.net.*: This package contains the set of core classes and interfaces for creating basic client and server sockets.

  • javax.net.ssl.*: This package contains the set of core classes and interfaces for creating secure client and server SSL sockets, and for creating secure HTTP URL connections.

  • javax.security.cert.*: This package is the same as the Java certification path API, which supports JSSE with basic certificate management functions.

Let's take a look at some of the most important classes from the JSSE API framework:

  • SocketFactory (javax.net.SocketFactory): A factory class for creating Socket objects. The ServerSocketFactory is analogous to the SocketFactory class, but it is specific to creating server sockets.

  • SSLSocket (javax.net.ssl.SSLSocket): Represents a Socket that supports SSL and TLS protocols.

  • SSLServerSocket: Extends the ServerSocket and provides secure server sockets using protocols such as the Secure Sockets Layer (SSL) or Transport Layer Security (TLS) protocols.

  • SSLEngine: An abstract class that allows implementing transport-independent secure communications using SSL/TLS protocols. This class is available from J2SE 5.0 and later.

  • SSLSocketFactory (javax.net.ssl.SSLSocketFactory): A factory class for creating SSLSocket objects. To create an SSLSocket, the JSSE provider must be configured by setting appropriate values to the ssl.SocketFactory.provider property in java.security properties. The SSLServerSocketFactory is analogous to the SSLSocketFactory class, but it is specific to creating SSL-based server sockets.

  • SSLSession (javax.net.ssl.SSLSession): Represents the session attributes that describe the session negotiated between two communicating peers. The session context contains attributes such as the shared master secret key, network address of the remote peer, time, usage, and so forth.

  • SSLSessionContext (javax.net.ssl.SSLSessionContext): Represents a set of SSLSession objects associated with a communicating peer, which can be a server or a client.

  • SSLSessionBindingEvent (javax.net.ssl.SSLSessionBindingEvent): Represents an event object that encapsulates SSL session binding and unbinding objects.

  • SSLSessionBindingListener (javax.net.ssl.SSLSessionBindingListener): Represents a listener interface, implemented by objects, which listens to SSLSession binding or unbinding events.

  • TrustManager (javax.net.ssl.TrustManager): Represents an interface to determine whether the presented authentication credentials from the remote identity should be trusted or not trusted.

  • TrustManagerFactory (javax.net.ssl.TrustManagerFactory): An engine class for a JSSE provider that acts as a factory for one or more TrustManager objects. For SunJSSE provider, it returns a basic X.509 trust manager.

  • HttpsURLConnection (javax.net.ssl.HttpsURLConnection): Represents an HttpURLConnection that supports the SSL and TLS protocols.

  • HostnameVerifier (javax.net.ssl.HostnameVerifier): Represents an interface class for hostname verification used for verifying the authenticity of the requests from the originating host. In an SSL handshake, if the URL's hostname and the server's identification hostname mismatch, the verification mechanism uses this interface to verify the authenticity of the connection and its originating host.

Understanding the JSSE API Programming Model

For more information about the JSSE API programming model and the steps involved, we will take a look at some common JSSE usage scenarios, such as secure socket connection using SSL, client and server mutual authentication, proxy tunneling, HTTP over SSL communication, and peer host name verification.

Secure Socket Connection Using SSL

Let's take a look at a client and server communication scenario using the SSL and J2SE default keystore and truststore for storing certificates. The SSL server uses the keystore that contains its private key and corresponding public key. The SSL client uses the server's certificate stored in the truststore to verify the authenticity of a communicating peer. For more information about setting up KeyStore and TrustStore and generating public and private keys using the KeyTool utility, refer to the section entitled "KeyTool" in Chapter 3.

JSSE Client-side Communication

The programming steps involved in creating an SSL socket for a client-side application to communicate with a server using SSL is as follows:

1.

Register the JSSE provider.

2.

Create an instance of the SSLSocketFactory.

3.

Create an SSLSocket specifying the hostname and port.

4.

Create streams to securely send and receive data to and from the server.

5.

Close the streams.

6.

Close the socket.

When an SSL client socket connects to an SSL server, it receives a certificate of authentication from the server. The client socket then validates the certificate against a set of certificates in its truststore. The default truststore is <java-home>/jre/lib/security/cacerts, and a user-specific truststore can also be specified using the javax.net.ssl.trustStore system property. To validate the server's certificate on the client side, the server's certificate must be imported to the truststore before trying out the connection.

The following example MySSLClient.java (see Example 4-19) shows a client establishing an SSL connection with a server to send and receive messages.

Example 4-19. MySSLClient.java
import java.io.*; import javax.net.ssl.*; import com.sun.net.ssl.*; public class MySSLClient {   public static void main(String [] args)                                 throws Exception {    String servername = "localhost";    int sslport = "443";    // Register to use Sun JSSE as SSL provider    // Specify the Client truststore and its password    static {     Security.addProvider(new          com.sun.net.ssl.internal.ssl.Provider());    System.setProperty ("javax.net.ssl.trustStore",                                           "cacerts");   System.setProperty ("javax.net.ssl.trustStorePassword",                                  "changeit");  } try {      // Create an instance of a SocketFactory     SSLSocketFactory sslsocketfactory =       (SSLSocketFactory) SSLSocketFactory.getDefault();   // Create a Socket   SSLSocket sslsocket    = (SSLSocket)sslsocketfactory.createSocket(servername,                                                 sslport);   System.out.println("SSL Connection Established                                        with server");   // Create the streams to send and receive data   // using Socket      OutputStream out = sslSocket.getOutputStream();      InputStream in = sslSocket.getInputStream(); // Send messages to the server using the OutputStream // Receive messages from the server using the InputStream       //...     }      catch (Exception e) {       e.printStackTrace();      }   } } 

JSSE Server-side Communication

The programming steps involved in creating an application that acts as a server and communicates with a client using secure sockets are as follows:

1.

Register the JSSE provider.

2.

Create an instance of the SSLServerSocket Factory.

3.

Create an SSL Server Socket specifying the port.

4.

Listen for client SSL connections.

5.

Create streams to securely send and receive data to and from the client.

6.

Close the streams.

7.

Close the socket.

Creating an SSL server socket requires that the server has certificates that it will send to clients for authentication. The certificates must be contained in a keystore explicitly specified using the javax.net.ssl.keyStore system property.

The following code fragment example (see Example 4-20) describes how to create an SSL server socket and listen to client connections.

Example 4-20. MySSLServer.java
import java.io.*; import javax.net.ssl.*; import com.sun.net.ssl.*; public class MySSLServer { public static void main(String [] args)                                   throws Exception {  int sslport = "443";    // Register to use Sun JSSE as SSL provider    // Specify the server's keystore and its password    static {      Security.addProvider(new            com.sun.net.ssl.internal.ssl.Provider());      System.setProperty("javax.net.ssl.keyStore",                                           "keystore");      System.setProperty("javax.net.ssl.keyStorePassword",                                              "changeit");    }  try {   // Create an instance of an SSL Server SocketFactory    SSLServerSocketFactory sslServerSocketfactory          = (SSLServerSocketFactory)                 SSLServerSocketFactory.getDefault();   // Create an SSL Server Socket    SSLServerSocket sslServerSocket     = (SSLServerSocket)       sslServerSocketfactory.createServerSocket(sslport);   while (true) {      // Accept client connection        SSLSocket sslsocket =             (SSLSocket)sslServerSocket.accept();       InputStream inputstream                          =  sslsocket.getInputStream();       InputStreamReader inputstreamreader                     = new InputStreamReader(inputstream);       BufferedReader bufferedreader                 = new BufferedReader(inputstreamreader);       String test;    while((test = bufferedreader.readLine()) != null){       System.out.println(test);      }    }    }    catch (Exception e) {         e.printStackTrace();    }   } } 

Mutual Authentication

The mutual authentication in a secure communication adds the value of a client being able to authenticate a server. This provides a means whereby a client can verify a server's authenticity and trust the data exchanged from the server.

In a mutual authentication process, both client and server exchange their certificates and thereby create a trusted communication channel between them. When an SSL client socket connects to an SSL server, it receives a certificate of authentication from the server. The client socket validates the certificate against a set of certificates in its truststore. Then the client sends its certificate of authentication to the server, which the server validates against a set of certificates in its truststore. Upon successful validation, a secure communication is established. To validate the server's certificate on the client side and the client's certificate on the server side, the server's certificate must be imported beforehand to the client's truststore and the client's certificate must be imported to the server's truststore.

In JSSE, enabling client-based mutual authentication to authenticate the server can be done by setting SSLServerSocket.setNeedClientAuth(true). To enforce client authentication, requesting the client to furnish the peer client certificate is done by setting SSLServerSocket.setWantClientAuth(true).

The following code fragment (see Example 4-21) shows how to force SSL server socket connections that request client certificate authentication (mutual authentication).

Example 4-21. Establishing mutual authentication using client certificates
// 1. Create an SSL Server Socket  SSLServerSocket sslServerSocket = (SSLServerSocket)      sslServerSocketfactory.createServerSocket(sslport); // 2. To force requesting client's certificate from server      sslServerSocket.setWantClientAuth(true); 

Additionally, we need to specify KeyStore and TrustStore properties as command-line options or system properties (see Example 4-22) in both client and server environments. For example (in the client environment):

Example 4-22. Setting up the Keystore and Truststore properties
java -Djavax.net.ssl.trustStore=client-cacerts \ -Djavax.net.ssl.trustStorePassword=changeit \ -Djavax.net.ssl.trustStoreType=JCEKS \ -Djavax.net.ssl.keyStore=client-keys \ -Djavax.net.ssl.keyStorePassword=changeit \ -Djavax.net.ssl.keyStoreType=JCEKS MySSLClient 

HTTP Over SSL (HTTPS) Using JSSE

HTTP over SSL allows establishing secure HTTP communications using SSL/TLS sockets. With JSSE, the procedure for creating HTTPS connections is similar to that for HTTP connections, except that you must register the JSSE provider and its associated HTTPS protocol handler and configure SSL parameters before trying out the connection.

The following code fragment (see Example 4-23) walks through the steps involved in creating a client that's required to open an HTTPS connection with an SSL-enabled HTTP server capable of handling https:// URLs.

Example 4-23. Creating an HTTP/SSL connection from a client
    HttpsURLConnection urlConnection = null;   // 1.Dynamically register the JSSE provider.     java.security.Security.addProvider(new              com.sun.net.ssl.internal.ssl.Provider());   // 2.Use Sun's JSSE implementation of the HTTPS handler      System.setProperty("java.protocol.handler.pkgs",                "com.sun.net.ssl.internal.www.protocol");  // 3.Get the handle to HTTPS URL and its connection   url = new URL("https://www.coresecuritypatterns.com");   urlConnection           = (HttpsURLConnection)url.openConnection(); 

Setting Timeouts in a URLConnection

With the release of J2SE 5.0, the JSSE API now provides a way of setting timeouts on connect and read operations for protocol handlers. This will benefit any HTTP client application that must behave robustly in the event of server failure. The new methods for setting and getting timeouts are included with URLConnection as setConnectTimeout(int timeout), int getConnectTimeout(), setReadTimeout(int timeout), and int getReadTimeout().

Proxy Tunneling

Proxy tunneling provides a new level of communication security when two parties decide on communicating across the Internet. When the communication layer and data exchanged is not encrypted, it becomes easy to attack and identify the communication endpoints, sender/receiver information, and the conversation from the packets. Proxy tunneling provides a mechanism that allows access to a resource behind a firewall via a proxy server. The proxy server hides the addresses of the communicating hosts on its subnet from the outside attackers and protects the communication from those attacks.

JSSE provides proxy tunneling support for accessing applications behind a firewall. This allows access using HTTP only via a proxy server. To enable proxy tunneling, the JSSE requires the application to specify the https.ProxyHost and https.ProxyPort as system properties. To exclude selected hosts to connect without using a proxy, add http.nonProxyHosts as a system property.

Let's take a look at the following code example (HTTPSClientUsingProxyTunnel.java) that walks through the steps involved in tunneling through a proxy server using HttpsURLConnection (HTTP over SSL connection) with an SSL-enabled HTTP server:

Example 4-24. HTTPSClientUsingProxyTunnel.java
public class HTTPSClientUsingProxyTunnel {      String proxyHost = "myproxy.com";      String proxyPort = "8080";   public static void main(String[] args)                               throws Exception {    // Register the JSSE provider and HTTPS handlers    // Specify client truststore properties    static {      Security.addProvider(new           com.sun.net.ssl.internal.ssl.Provider());      System.setProperty("javax.net.ssl.trustStore",                                         "cacerts");      System.setProperty          ("javax.net.ssl.trustStorePassword",                                         "changeit");      System.setProperty("java.protocol.handler.pkgs",               "com.sun.net.ssl.internal.www.protocol");     }    URL httpsURL = new URL("https://www.verisign.com");    // Open a HTTPS Connection    URLConnection urlConnection                           = httpsURL.openConnection();    if(urlConnection instanceof       com.sun.net.ssl.HttpsURLConnection) {  ((com.sun.net.ssl.HttpsURLConnection)urlConnection).        SetSSLSocketFactory(new           SSLTunnelSocketFactory(proxyHost,proxyPort));       }    BufferedReader in = new BufferedReader(new       InputStreamReader(urlConnection.getInputStream()));    String input;       while ((input = in.readLine()) != null)       System.out.println(input);       in.close();       }   } 

Host Name Verification Using JSSE

Host name verification is a mechanism that helps prevent man-in-the-middle attacks by validating that the host to which an SSL connection is made is the intended or authorized party or a trusted host. Host name verifier is quite useful when a client or a server instance is acting as an SSL client for another server. During an SSL handshake, if the URL's host name and the identified host name mismatch, the verification mechanism can call back to determine if this connection should be allowed.

In a JSSE-based SSL communication, host name verification can be enabled by setting HttpsURLConnection.setHostnameVerifier(HostnameVerifier hnv). The following code snippet (see Example 4-25) shows how to set a HostnameVerifier in a JSSE-based SSL connection to verify a peer host.

Example 4-25. Enabling Host name verification in SSL communication
URL url = new URL("https://myserver.sunnyday.com"); HttpsURLConnection mycon           = (HttpsURLConnection) url.openConnection(); // Set the host name verifier   mycon.setHostnameVerifier(new HostnameVerifier() { // Verify the SSL Peer  public boolean verify( String urlHost,                                  SSLSession ssls ){  if( !urlHost.equals( ssls.getPeerHost() ) ){     System.out.println( "Alert: SSL host "        + ssls.getPeerHost() + "does not match URL host"                                             + urlHost);    }   return true;  }  } ); mycon.setDoInput(true); mycon.setDoOutput(true); mycon.setUseCaches(false); mycon.setDefaultUseCaches(false); return mycon; 

SSLEngine and Non-Blocking I/O

In the release of J2SE 5.0, JSSE introduced a new abstraction class that allows applications to use the SSL/TLS protocols in a transport-independent way, thus freeing applications to choose transport, I/O, and threading models that best meet their needs. This allows applications to use a wide variety of I/O types, such as non-blocking I/O (polling), selectable non-blocking I/O, Socket and the traditional Input/OutputStreams, local ByteBuffers or byte arrays, and so forth.

Using the SSLEngine

An SSLEngine is created by calling SSLContext.createSSLEngine() from an initialized SSLContext. All configuration parameters should be set before initiating the call to wrap(), unwrap(), or beginHandshake(). These methods all trigger the initial handshake. Data moves through the engine by calling wrap() or unwrap() on outbound or inbound data, respectively. Depending on the state of the SSLEngine, a wrap() call may consume application data from the source buffer and may produce network data in the destination buffer. The outbound data may contain application and/or handshake data. A call to unwrap() will examine the source buffer and may advance the handshake if the data is handshaking information, or may place application data in the destination buffer if the data is application information. The state of the underlying SSL/TLS algorithm will determine when data is consumed and produced.

Example 4-26 shows the steps in typical usage.

Example 4-26. Using SSLEngine
// 1. Use KeyManager to identify the key to use. KeyManagerFactory kmf =     KeyManagerFactory.getInstance("SunX509"); kmf.init(ksKeys, passphrase); // 2. Use TrustManager's to allow connections. TrustManagerFactory tmf =     TrustManagerFactory.getInstance("SunX509"); tmf.init(ksTrust); // 3. Initialize the SSLContext with key material sslContext = SSLContext.getInstance("TLS"); sslContext.init(     kmf.getKeyManagers(), tmf.getTrustManagers(), null); // 4. Create the SSLEngine. SSLEngine engine = sslContext.createSSLengine(hostname, port); // 5. Use as client engine.setUseClientMode(true); // 6. Create a non-blocking socket channel SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress(hostname, port)); // 7. Complete connection while (!socketChannel.finishedConnect()) {     // do something until connect completed } // 8. Use byte buffers to hold application/encoded data SSLSession session = engine.getSession(); ByteBuffer myAppData =  ByteBuffer.allocate(session.getApplicationBufferSize()); ByteBuffer myNetData =    ByteBuffer.allocate(session.getPacketBufferSize()); ByteBuffer peerAppData =  ByteBuffer.allocate(session.getApplicationBufferSize()); ByteBuffer peerNetData =   ByteBuffer.allocate(session.getPacketBufferSize()); // 9. Do initial handshake doHandshake(socketChannel, engine, myNetData, peerNetData); myAppData.put("hello world".getBytes()); myAppData.flip(); while (myAppData.hasRemaining()) { // 10. Generate SSL/TLS encoded data //     (handshake or application data)     SSLEngineResult res = engine.wrap(myAppData, myNetData);     // Process status of call } 

The SSLEngine produces or consumes complete SSL/TLS packets only and it does not store application data internally between calls to wrap() or unwrap(). Thus, input and output ByteBuffers must be sized appropriately to hold the maximum record that can be produced. Calls to SSLSession.getPacketBufferSize() and SSLSession.getApplicationBufferSize() should be used to determine the appropriate buffer sizes. The size of the outbound application data buffer generally does not matter. If buffer conditions do not allow for the proper consumption/production of data, the application must determine the problem using SSLEngineResult, correct the problem, and then retry the call again. Unlike SSLSocket, all methods of SSLEngine are non-blocking. SSLEngine implementations may cause the results of tasks that may take an extended period of time to complete, or may be seemingly blocking or slow. For any operation which may potentially block, the SSLEngine will create a runnable_delegated task using a thread depending on the design strategy.

To shut down an SSL/TLS connection, the SSL/TLS protocols require the transmission of close messages. Therefore, when an application is done with the SSL/TLS connection, it should first obtain the close messages from the SSLEngine, transmit them to the communicating peer using its transport mechanism, and then finally shut down the transport mechanism.

So far we have looked at JSSE and how to use its secure communication services. Now, let's explore the Java Authentication and Authorization Service (JAAS), which provides API mechanisms for authentication and authorization services.




Core Security Patterns. Best Practices and Strategies for J2EE, Web Services, and Identity Management
Core Security Patterns: Best Practices and Strategies for J2EE, Web Services, and Identity Management
ISBN: 0131463071
EAN: 2147483647
Year: 2005
Pages: 204

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net