Recent top five:
Java.next -- Four languages that represent the future of Java
Blogger Stuart Halloway has begun a series of posts on trends that point to the future of the Java platform. In his first
post, he compares Clojure, Groovy, JRuby, and Scala -- four wildly different languages that nonetheless all play together
in the JRE. Find out what unites these languages and what they can tell us about the future of Java-based development ...
| Enterprise AJAX - Transcend the Hype |
| Memory Analysis in Eclipse |
| Oracle Compatibility Developer's Guide |
| Memory Analysis in Eclipse |
Before the JSSE framework can recognize any JSSE providers, you must register them permanently in the <java home>\jre\lib\security\java.security properties file or register them dynamically by calling the Security.addProvider() method in your code:
Security.addProvider( new com.sun.net.ssl.internal.ssl.Provider());
Then you need to replace JavaMail's default socket factory with JSSE's SSL socket factory. This approach is similar to the one used in "Java Tip 96: Use HTTPS in Your Java Client Code"; however, in our case, we have no control over socket creation since sockets are created inside the JavaMail framework, not in the application code or storage provider. Fortunately, JavaMail accepts several undocumented properties, allowing you to set up a custom socket factory class and some other parameters. Those properties are:
mail.<protocol>.socketFactory.class mail.<protocol>.socketFactory.fallback mail.<protocol>.socketFactory.port mail.<protocol>.timeout
Now you can use the following code to replace a socket factory:
final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"; Properties props = System.getProperties(); // IMAP provider props.setProperty( "mail.imap.socketFactory.class", SSL_FACTORY); // POP3 provider props.setProperty( "mail.pop3.socketFactory.class", SSL_FACTORY); // NNTP provider (if any) // props.setProperty( "mail.nntp.socketFactory.class", SSL_FACTORY);
If you want JavaMail to use only secure connections, you configure the appropriate properties so JavaMail doesn't fall back to an unsecure connection when a secure one fails:
// IMAP provider props.setProperty( "mail.imap.socketFactory.fallback", "false"); // POP3 provider props.setProperty( "mail.pop3.socketFactory.fallback", "false"); // NNTP provider (if any) // props.setProperty( "mail.nntp.socketFactory.fallback", "false");
You then change the default port number to the corresponding port that your protocol's secure version uses; otherwise, you
must use a fully qualified address (that includes a port number) in the URL passed to JavaMail (for example, imap://id:password@your.imap.server.com:993/folder/), or else you get an "unrecognized SSL handshake" exception. You specify these properties like so:
// IMAP provider props.setProperty( "mail.imap.port", "993"); props.setProperty( "mail.imap.socketFactory.port", "993"); // POP3 provider props.setProperty( "mail.pop3.port", "995"); props.setProperty( "mail.pop3.socketFactory.port", "995"); // NNTP provider (if any) // props.setProperty( "mail.pop3.port", "563"); // props.setProperty( "mail.pop3.socketFactory.port", "563");
After setting up all the properties, you can open a secure JavaMail session:
Session session = Session.getInstance(props);
Unfortunately, you may realize that the code above throws an SSLException "untrusted server cert chain" if the mail server certificate is not installed locally. In that case, you should obtain a
correct, valid certificate for the server and use the keytool utility to add it to a local key storage at <javahome>\jre\lib\security\cacerts.
Alternatively, you can replace the default TrustManager. However, since you can't control socket creation and you want your TrustManager to work, you must use your own socket factory with SSLSocketFactory, which creates secure sockets.
Our simple example TrustManager implementation, DummyTrustManager.java, accepts all certificates without validation, even if the certificate name differs from the server name. This could be a
security issue you may want to address in your application. For example, you can show a nice GUI dialog asking if the user
trusts the certificate, and then store the certificate locally (you've probably seen these dialogs in Netscape Navigator or
MS Internet Explorer).
Here is the code:
import com.sun.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
public class DummyTrustManager implements X509TrustManager {
public boolean isClientTrusted( X509Certificate[] cert) {
return true;
}
public boolean isServerTrusted( X509Certificate[] cert) {
return true;
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[ 0];
}
}
You also need to implement a custom SSLSocketFactory, like DummySSLSocketFactor.java. We will use the Proxy pattern to change the TrustManager of a standard SSLSocketFactory:
import com.sun.net.ssl.*;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
public class DummySSLSocketFactory extends SSLSocketFactory {
private SSLSocketFactory factory;
public DummySSLSocketFactory() {
System.out.println( "DummySocketFactory instantiated");
try {
SSLContext sslcontext = SSLContext.getInstance( "TLS");
sslcontext.init( null, // No KeyManager required
new TrustManager[] { new DummyTrustManager()},
new java.security.SecureRandom());
factory = ( SSLSocketFactory) sslcontext.getSocketFactory();
} catch( Exception ex) {
ex.printStackTrace();
}
}
public static SocketFactory getDefault() {
return new DummySSLSocketFactory();
}
public Socket createSocket( Socket socket, String s, int i, boolean
flag)
throws IOException {
return factory.createSocket( socket, s, i, flag);
}
public Socket createSocket( InetAddress inaddr, int i,
InetAddress inaddr1, int j) throws IOException {
return factory.createSocket( inaddr, i, inaddr1, j);
}
public Socket createSocket( InetAddress inaddr, int i) throws
IOException {
return factory.createSocket( inaddr, i);
}
public Socket createSocket( String s, int i, InetAddress inaddr, int j)
throws IOException {
return factory.createSocket( s, i, inaddr, j);
}
public Socket createSocket( String s, int i) throws IOException {
return factory.createSocket( s, i);
}
public String[] getDefaultCipherSuites() {
return factory.getSupportedCipherSuites();
}
public String[] getSupportedCipherSuites() {
return factory.getSupportedCipherSuites();
}
}
Finally you should install the SocketFactory provider like this:
Security.setProperty( "ssl.SocketFactory.provider", "DummySSLSocketFactory");
You should execute this code before creating any JavaMail sessions. Also, don't forget to make the DummySSLSocketFactory class visible to your code.
We've created a sample application, called FolderList.java, based on the JavaMail mail folder listing demo, which uses the above code. It shows that you can securely connect to the mail server using JSSE by configuring the JavaMail framework properly. With this approach, you're ready to make your JavaMail-based application support secure communication without any significant changes in your own code.