Today I was playing around with the Java Mail API using channel security and hit a snag. Our SMTP server supports no security and TLS. Everything was great using no security, but using TLS we get

javax.mail.MessagingException: Can't send command to SMTP host;

nested exception is: PKIX path building failed: unable to find valid certification path to requested target

The problem here was the SMTP server certificate: it was untrusted. This will happen every time with self-signed certificates. In my case, the certificate was signed by an untrusted certificate authority (the CA certificate itself was self-signed.)

I had two problems: I need to trust the server certificate, but more difficult than that was that I needed to actually obtain the certificate because I didn’t have it.

Problem one: obtaining the SMTP server certificate

The easiest way to obtain these certificates is to use openssl’s s_client. s_client is provided with openssl to debug SSL/TLS servers. You are able to observe the entire protocol execution and obtain most of the information you need to resolve whatever problem you’re facing. In my case, for the SMTP + TLS scenario, I just ran
openssl s_client -connect -starttls smtp > smtp.txt
This will take a long time to complete and will seem stuck, but you can cancel it soon after the beginning of the execution as you’ll already have the certificate.

For the SMTP + SSL scenario, I ran

openssl s_client -connect > smtp.txt
This actually seemed stuck earlier than the TLS command, but like that one you get the certificate right in the beginning. Feel free to cancel it.

Open the smtp.txt file. You will see a certificate chain followed by Server certificate. Copy the bit starting with BEGIN CERTIFICATE and END CERTIFICATE into a smtp.crt file. Opening this file you should be able to check the certificate properties and its respective thumbprint.

Congratulations, you now have the server certificate. Now you just need to trust it.

Problem two: trusting the certificate

There are two options for trusting this certificate: you can add it to the cacerts keystore in your JVM so that all java apps can trust it or you can add it to a separate keystore and specify in your code to use this keystore.

I followed the first approach as this certificate was actually ok to be trusted. So to trust this certificate, open a terminal window, go to <jre_home>/lib/security and type

keytool -import -keystore cacerts -alias <alias> -file smtp.crt
No need to specify alias, but the default is mykey which made it nearly certain I wouldn’t remember what certificate this was supposed to be. It can be anything, though.

And you’re done. Remember, you need to execute the keytool import into the cacerts for the target JVM!