SMTP+SSL/TLS時,如何設定trusted host?

在整合SMTP功能時,如果要提供SSL或StartTLS的方式與SMTP server溝通,一定會遇到certificate hostname驗證的問題;因為內部的mail server不一定會提供合法的certificate。例外可能會像這樣:

Caused by: java.io.IOException: Server is not trusted: tonylin.idv.tw
	at com.sun.mail.util.SocketFetcher.configureSSLSocket(SocketFetcher.java:564)
	at com.sun.mail.util.SocketFetcher.startTLS(SocketFetcher.java:486)
	at com.sun.mail.smtp.SMTPTransport.startTLS(SMTPTransport.java:1902)
	... 30 more
本篇文章,主要分享透過程式將目標機器設定為可信任的方式。

第一個方法是設定property(必須沒設定SocketFactory,而javamail預設會使用MailSSLSocketFactory):

prop.put("mail.smtp.ssl.trust", "tonylin.idv.tw");
有多個可以使用空白分格,如果要允許全部就使用*。

第二個方法是直接使用MailSSLSocketFactory,針對SSL的情況要使用mail.smtp.socketFactory:
MailSSLSocketFactory socketFactory = new MailSSLSocketFactory();
socketFactory.setTrustedHosts(new String[]{"tonylin.idv.tw"});
socketFactory.setTrustAllHosts(true);
prop.put("mail.smtp.socketFactory", socketFactory);
而StartTLS會經過將普通連線變為TLS連線的階段,所以要使用mail.smtp.ssl.socketFactory:
prop.put("mail.smtp.starttls.enable","true");	
prop.put("mail.smtp.ssl.socketFactory", socketFactory);

為了可以針對是否驗證做切換,我是這樣處理:
private SocketFactory getMailSSLSocketFactory() {
	try {
		if (ignoreCertValidation) {
			MailSSLSocketFactory factory = new MailSSLSocketFactory();
			factory.setTrustAllHosts(true);
			return factory;
		}
		return SSLSocketFactory.getDefault();
	} catch (GeneralSecurityException e) {
		throw new RuntimeException(e);
	}
}
 
switch (getEncryption()) {
case SSL:
	prop.put("mail.smtp.socketFactory", getMailSSLSocketFactory());
	break;
case TLS:
	prop.put("mail.smtp.ssl.socketFactory", getMailSSLSocketFactory());
	prop.put("mail.smtp.starttls.enable", "true");
	break;
default:
	break;
}

除了以上方法,你也可以使用自己SocketFactory去解決這問題。我們也有程式可以在runtime將目標機器的certificate加到keystore中。