Programmatic SSL

SSL clients are Java programs that use SSL-enabled connections to communicate with WebLogic Server. These clients could be running within WebLogic itself for instance, as a servlet. WebLogic uses the Certicom JSSE extensions in its implementation of the JSSE, and provides APIs that ease the task of communicating over an SSL channel. In fact, the APIs let you create SSL-enabled socket connections, URL connections over SSL, and JNDI contexts using certificate-based authentication. You don't have to use WebLogic's implementation of the JSSE in Java clients, though what follows in this section assumes that you are. For server-side code, you have to use WebLogic's JSSE.

At the time of publishing this book, WebLogic 7.0 is certified only for JKD 1.3 and not for JDK 1.4. You will experience SSL problems if you use JDK 1.4 to run your clients and/or the server. An unofficial way around some of these problems is to delete the file jrelibjsse.jar that is shipped with your JDK 1.4 distribution. You do not need to do this if you are using WebLogic 8.1.

Whenever you run an SSL-enabled Java client, it is very important that you supply the correct values for the environment settings. Without these system properties, your Java clients surely will fail. For instance, any Java client that needs to use WebLogic's SSL implementation must set the following system property:

-Djava.protocol.handler.pkgs=com.certicom.net.ssl

Otherwise, the client may be using an alternative SSL implementation; therefore, you may get the following exception when creating an HTTPS URL connection:

Exception in thread "main" java.net.MalformedURLException: unknown protocol: https

If the client is intended to run within WebLogic itself, it implicitly relies on WebLogic's SSL implementation and you do not need to set this system property. Also, if the client needs to validate an incoming certificate, you must ensure that the client is configured with the location of a keystore that holds a number of root CA certificates:

-Dweblogic.security.SSL.trustedCAKeyStore=x:serverlibcacerts

Otherwise, the Java client will experience handshake errors similar to this:

java.io.IOException: Write Channel Closed, possible SSL handshaking or trust failure

Similarly, if the Java client attempts to make an SSL connection to a server that requires two-way SSL, and the client fails to present a digital certificate as proof of its identity, the client will experience a fatal handshake error indicating the reason for dropping the connection:

javax.net.ssl.SSLHandshakeException: FATAL Alert:HANDSHAKE_FAILURE - The handshake handler was unable to negotiate an acceptable set of security parameters.

16.3.1 URL Connections

The weblogic.net.http package contains two useful classes for establishing URL connections. Use an HttpURLConnection object to establish plain-text URL connections and an HttpsURLConnection object to set up SSL-protected URL connections. The following example illustrates how to create a plain URL connection to a resource on a remote server:

URL url = new URL("http", host, portnumber, "/index.html"); weblogic.net.http.HttpURLConnection connection = new weblogic.net.http.HttpURLConnection(url); connection.connect( ); // ... Work with the connection, open inputstream, etc. ... System.err.println(con.getResponseCode( )); InputStream in = con.getInputStream( ); byte buf[] = new byte[256]; int numRead ; while ( (numRead = in.read(buf)) != -1 ) { System.out.write(buf, 0, numRead); } connection.disconnect( );

Once the client has connected to the specified URL, it obtains the response code and uses the input stream from the HttpURLConnection object to read and print the contents of the HTML page. In order to make an SSL connection to a resource on a server, you need to create an HttpsURLConnection object that connects to the specified URL. The following example shows how to create an SSL connection to the same HTML page, but this time uses the https protocol:

URL url = new URL("https", host, port, "/index.html"); weblogic.net.http.HttpsURLConnection sconnection = new weblogic.net.http.HttpsURLConnection(url); sconnection.connect( ); // ... Work with the connection, open inputstream, etc. ... sconnection.disconnect( );

However, if the server was configured for two-way SSL authentication, you need to use the HttpsURLConnection object to supply additional SSL resources from the client's end. The following code sample shows how to connect to an SSL-protected resource on a server configured for two-way SSL:

URL url = new URL("https", host, port, "/index.html"); weblogic.net.http.HttpsURLConnection con = new weblogic.net.http.HttpsURLConnection(url); // Now we create inputstreams to the key and certificate FileInputStream keyFile = new FileInputStream(KEYFILE); FileInputStream certFile = new FileInputStream(CERTFILE); con.loadLocalIdentity(certFile, keyFile, KEYPASSWORD.toCharArray( )); con.connect( ); // ... Work with the connection, open inputstream, etc. ... con.disconnect( );

In this case, we used the loadLocalIdentity( ) method to furnish the HttpsURLConnection object with the files that hold the client's digital certificate and private key. Of course, you also must specify the password used to protect the client's private key. For instance, you easily could use the PEM-encoded digital certificate and private key for the client:

//... FileInputStream keyFile = new FileInputStream("mykeyfile.pem"); FileInputStream certFile = new FileInputStream("mycertfile.pem"); con.loadLocalIdentity(certFile, keyFile, ("mypassword").toCharArray( )); //...

The loadLocalIdentity( ) method on an HttpsURLConnection object also can extract the client's private key from within a JKS keystore. In this case, use the standard java.security package to access and manipulate the keystore:

URL url = new URL("https", host, port, "/index.html"); weblogic.net.http.HttpsURLConnection con = new weblogic.net.http.HttpsURLConnection(url); // Load key and certificate from the keystore KeyStore ks = KeyStore.getInstance("jks"); ks.load(new FileInputStream(KEYSTORE_NAME), KEYSTORE_PASSWORD.toCharArray( )); PrivateKey key = (PrivateKey) ks.getKey(KEY_ALIAS, KEY_PASSWORD.toCharArray( )); Certificate[] certChain = ks.getCertificateChain(KEY_ALIAS); con.loadLocalIdentity(certChain, key); con.connect( ); // ... Work with the connection, open inputstream, etc. ... con.disconnect( );

Clearly, you need to specify the keystore location and the pass phrase used to protect the keystore in order to create a KeyStore object. Using this KeyStore instance, you can do the following:

You then can use this combination of the private key and the matching certificate chain to set up the client's identity. When a client invokes the connect( ) method on the HttpsURLConnection object, the server can use the key and certificate chain to authenticate the client and later negotiate an acceptable set of parameters for the SSL connection. Later, we shall see how to use the setHostnameVerifierJSSE( ) method on the HttpsURLConnection object to configure a custom hostname verifier for the client.

16.3.2 SSL Sockets

SSL sockets are the equivalent of TCP sockets they allow you to connect to an SSL-enabled port and then use one of the configured protocols to invoke a service available on the port. SSL sockets provide a low-level API for interacting with SSL-protected services. You need to be familiar with the underlying protocol before you can access any resource. Nevertheless, SSL sockets are extremely versatile and allow you to build powerful clients that can do more than just ordinary HTTPS connections.

The JSSE API plays a major role in setting up SSL socket connections. In particular, a client must create a javax.net.ssl.SSLSocket object to connect to the SSL port on a running server. Using WebLogic's SSL implementation, all of the client's SSL configuration revolves around the weblogic.security.SSL.SSLContext class. An SSLContext instance provides the context for any SSL socket connections you create:

Most importantly, once you've initialized the SSLContext object, you can use it to obtain an SSL socket factory. This SSLSocketFactory instance will allow you to establish socket connections to an SSL-enabled port on a running WebLogic instance.

16.3.2.1 Using SSLSocket

Let's look at how to use SSLContext to set up an SSL socket connection to the web server over HTTPS, and then interact with the port. In this case, the Java client will use a mixture of WebLogic's SSL implementation classes and the standard JSSE interfaces:

import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.Certificate; import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLSocket; import javax.security.cert.X509Certificate; import weblogic.security.SSL.HostnameVerifierJSSE; import weblogic.security.SSL.SSLContext; import weblogic.security.SSL.SSLSocketFactory; import weblogic.security.SSL.TrustManagerJSSE;

The SSLContext class provides a getInstance( ) factory method for creating an SSLContext object. In our case, we intend to set up an SSLContext object for socket connections to an HTTPS port:

SSLContext ctx = SSLContext.getInstance("https");

16.3.2.2 Using a custom hostname verifier

The next step is to assign a custom hostname verifier to the SSLContext object. A hostname verifier assures the client that the server's hostname to which the client connects matches the server name in the SubjectDN field of the server's digital certificate. WebLogic requires that any custom hostname verifier class implement the weblogic.security.SSL.HostnameVerifierJSSE interface. This interface exposes a single method, verify( ), which conveniently accepts two arguments: the server's hostname URL and the hostname embedded in the server's certificate. Here is a sample implementation for the hostname verifier:

HostnameVerifierJSSE myVerifier = new HostnameVerifierJSSE( ) { public boolean verify(String urlHostname, String certHostname) { return urlHostname.equals(certHostname); } }; ctx.setHostnameVerifierJSSE(myVerifier);

Notice how we've invoked the setHostnameVerifierJSSE( ) method to register our hostname verifier with the SSLContext object. Clearly, if the verify( ) method always returns true, you are programmatically mimicking what happens when you actually disable hostname verification from the command line:

-Dweblogic.security.SSL.ignoreHostnameVerification=false

16.3.2.3 Using a custom trust manager

For the next step, we assign a custom trust manager. A trust manager is invoked when an SSL peer presents a certificate chain to your client during the initial SSL connection handshake. Typically, you will implement this method so that the client can ignore certain errors in the certificate chain, thereby allowing the SSL handshake to continue in spite of these validation errors. WebLogic requires that any custom manager implement the weblogic.security.SSL.TrustManagerJSSE interface. This interface exposes a single callback method, certificateCallback( ) (which gets supplied with the server's chain of X.509-compliant certificates), and an indication of any validation errors. Here is our sample implementation for a trust manager, which ensures that the certificate validation always succeeds, but nevertheless prints out the subject and issuer of each certificate in the chain:

TrustManagerJSSE dumpTManager = new TrustManagerJSSE( ) { public boolean certificateCallback(X509Certificate[] chain, int validateErr) { for (int i = 0; i < chain.length; i++) { System.err.println("Issuer: " + chain[i].getIssuerDN( ).getName( )); System.err.println("Subject: " + chain[i].getSubjectDN( ).getName( )); } return true; }}; ctx.setTrustManagerJSSE(dumpTManager);

The TrustManagerJSSE interface defines the possible errors that can occur during certificate validation. For example, ERR_NONE indicates that no error occurred during certificate validation, while ERR_CERT_EXPIRED indicates that one of the certificates in the chain has expired.

When an SSL client connects to an SSL server, the SSL server presents its digital certificate chain to the client for authentication. That chain could contain an invalid digital certificate. The SSL specification says that the client should drop the SSL connection upon discovery of an invalid certificate. Web browsers, however, ask the user whether to ignore the invalid certificate and continue up the chain to determine whether it is possible to authenticate the SSL server with any of the remaining certificates in the certificate chain.

The trust manager eliminates this inconsistent practice by allowing you to control when to continue or discontinue an SSL connection. Using a trust manager, you can perform custom checks before continuing an SSL connection. For example, you can use the trust manager to specify that only users from specific localities (such as towns, states, or countries) or users with other special attributes can gain access via the SSL connection.

16.3.2.4 Setting up the client's identity

Finally, use the SSLContext object to establish the client's local identity, something that is needed only if the server is configured for two-way SSL. The following code sample shows how to equip the SSLContext object with the client's private key and certificate chain from a JKS keystore:

KeyStore ks = KeyStore.getInstance("jks"); ks.load(new FileInputStream(KEYSTORE_NAME), KEYSTORE_PASSWORD.toCharArray( )); PrivateKey key = (PrivateKey) ks.getKey(KEY_ALIAS, KEY_PASSWORD.toCharArray( )); Certificate[] certChain = ks.getCertificateChain(KEY_ALIAS); ctx.loadLocalIdentity(certChain, key);

Once again, we extract the private key and certificate chain from the JKS keystore and pass these to the loadLocalIdentity( ) method on the SSLContext object. Now that you've established an SSL context, you can ask for an SSL socket factory and use it to create SSL socket connections. The following code sample shows how to obtain an SSLSocketFactory instance and then use it to create an SSL socket:

SSLSocketFactory sf = (SSLSocketFactory) ctx.getSocketFactoryJSSE( ); SSLSocket sslSocket = (SSLSocket) sf.createSocket(hostname, portNumber); OutputStream out = sslSocket.getOutputStream( ); // Send a request String req = "GET /index.html HTTP/1.0 "; out.write(req.getBytes( )); InputStream in = sslSock.getInputStream( ); // Read any reply sslSock.close( );

Once you've successfully established an SSL socket, you can use the HTTP protocol to retrieve an HTML page from the web server. This is essentially how you'd program against the standard JSSE interfaces.

16.3.2.5 Using an SSL handshake listener

Another handy addition you can make to the SSLContext object is to register an SSL handshake listener, which enables the client to receive notifications when an SSL handshake sequence completes. A handshake listener provides the client with the ideal opportunity to access to the cipher suite negotiated between the two parties, the client's certificates, the server's certificates, and the SSL session and socket that triggered the event. You typically would register a handshake listener just after creating the SSL socket connection:

HandshakeCompletedListener hcl = new HandshakeCompletedListener( ) { public void handshakeCompleted(HandshakeCompletedEvent evt) { // React to event } }; sslSocket.addHandshakeCompletedListener(hcl);

16.3.3 JNDI Clients

Earlier, we encountered several instances of JNDI clients that set up a JNDI context while providing the login credentials of a valid WebLogic user. The following shows a simple variation to our typical JNDI client.

Hashtable env = new Hashtable( ); env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); env.put(Context.PROVIDER_URL, "t3s://10.0.10.10:9002"); env.put(Context.SECURITY_PRINCIPAL, "system"); env.put(Context.SECURITY_CREDENTIALS, "12341234"); Context ctx = new InitialContext(env);

In this case, we've used the T3S protocol to create an SSL-secure JNDI context. This means WebLogic will send its public certificate back to the client, which the client will validate before successfully establishing the JNDI context. As usual, this server-side authentication will occur automatically, so long as you have configured a list of trusted CA certificates for the client.

If the server is configured for two-way SSL, we can build Java clients that rely on certificate-based JNDI authentication. In this case, the client needs to use its private key and digital certificate. WebLogic lets you wrap the private key and digital certificate in a weblogic.security.PEMInputStream. Example 16-1 shows how you can use the weblogic.jndi.Environment class to establish the JNDI context.

Example 16-1. Certificate-based JNDI authentication

Environment env = new Environment( ); env.setProviderUrl("t3s://10.0.10.10:7002"); // Set the username and password env.setSecurityPrincipal("system"); env.setSecurityCredentials("12341234"); // Create PEMInputStreams around our key and certificate InputStream key = new PEMInputStream(new FileInputStream(CERT_KEYFILE)); InputStream cert = new PEMInputStream(new FileInputStream(CERT_CERTFILE)); // Configure this as our local identity in the environment env.setSSLClientCertificate(new InputStream[] { key, cert}); env.setSSLClientKeyPassword(CERT_KEYPASSWORD); // Now proceed as usual env.setInitialContextFactory(Environment.DEFAULT_INITIAL_CONTEXT_FACTORY); context = env.getInitialContext( ); // Use the context UserTransaction ut = (UserTransaction) context.lookup("javax.transaction.UserTransaction"); //...

As you can see, we've invoked setSSLClientCertificate( ) to assign the PEMInputStream objects that wrap the client's private key and digital certificate. The order in which the elements of the PEMInputStream array are filled also is predefined:

In addition, we've invoked the setSSLClientKeyPassword( ) method to specify the pass phrase used to protect the client's private key. There is a small caveat you must create a new Environment object each time you need to invoke the getIntitialContext( ) method with new user and security credentials. Once you specify the user's login and security (i.e., private key and digital certificate), this security context remains set within the Environment object and cannot be modified during its lifetime.

16.3.4 JCE

JDK 1.4.1 supports the Java Connector Architecture (JCA) and Java Cryptography Extensions (JCE) APIs. Together, these APIs provide Java clients with access to cryptographic functionality. The framework is pluggable, allowing you to install a third-party provider that overrides the default provider that is supplied with the JDK. Different providers, for example, could supply additional cryptographic algorithms or engines. Although WebLogic does not supply a JCE provider, it does support the JCE implementation shipped with Sun's JDK 1.4.1 and nCipher's JCE provider. nCipher's JCE provider allows you to offload (and optimize) the usual server-side SSL processing to additional hardware, thereby freeing up more resources for WebLogic. Please visit http://www.ncipher.com for more details about the nCipher product.

Категории