Using Java Mail StartTLS with a Truststore - java

I'm trying to connect to a mail server which uses StartTLS with a self signed certificate via Java mail API.
And that seems to be a problem, because i can't find any way to set accepted certificates or a truststore for StartTLS.
Properties props = new Properties();
props.put("mail.imap.starttls.enable", "true");
props.put("mail.imap.starttls.required", "true");
Session session = Session.getInstance(props);
Store store = session.getStore("imap");
store.connect(hostName, port, userName, userPassword);
When i run my application as is, i get this PKIX path error:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
I would prefer not to use VM parameters like "-Djavax.net.ssl.trustStore" because i want to be able to control trusted certificates per access.
Sidenote: I've seen people use "mail.imap.socketFactory.class" to set their own implementation of SocketFactory with a self defined TrustManager.
But when i do that my connection fails with
javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
I think this is because setting the socket factory will actually use SMTP over SSL instead of StartTLS (which starts as a plain text connection and switches to TLS later).

I have this working for an SMTP connection (not IMAP) using com.sun.mail:javax.mail:1.5.5 and (root) certificates loaded from a (not so standard) pfx-file. The properties given to Session.getInstance(props) are build in the following manner (see also the API-docs here and here, I think you can simply replace smtp with imap for most of the properties):
"mail.transport.protocol", "smtp"
"mail.smtp.host", "hostname"
"mail.smtp.port", "25"
"mail.smtp.connecttimeout", "5000" // 5 seconds
"mail.smtp.timeout", "50000" // 50 seconds
"mail.smtp.ssl.protocols", "TLSv1.2"
"mail.smtp.starttls.required", "true"
Now build a SSL Socket Factory using com.sun.mail.util.MailSSLSocketFactory (read the API-docs in the link):
MailSSLSocketFactory sslSocketFactory = new MailSSLSocketFactory("TLSv1.2");
Create and intialize a (default) KeyManagerFactory kmf (e.g. by loading a keystore).
Create and intialize a (default) TrustManagerFactory tmf.
Call sslSocketFactory.setKeyManagers(kmf.getKeyManagers()) and sslSocketFactory.setTrustManagers(tmf.getTrustManagers())
Set property "mail.smtp.ssl.socketFactory" to the sslSocketFactory instance (use the props.put(k,v)-method). Note that the socket factory instance that was created and configured is set, not some String or class. Javamail will use the set socket factory instance directly.
Use the properties to create a session.
Make sure you have logging configured properly and set it to TRACE for com.sun.mail. Logging shows exactly what is "going over the line" and in my case shows for example:
DEBUG com.sun.mail.smtp - Found extension "STARTTLS", arg ""
...
TRACE com.sun.mail.smtp.protocol - STARTTLS
TRACE com.sun.mail.smtp.protocol - 220 Ready to start TLS
On a side note: creating a default keystore and trustore can be done using this SslUtils class, methods loadKeyStore(null) and createDefaultTrustStore() (I created this utility class a while ago to help me load the not-so-standard pfx-files).

Related

rc=2059 amq9503 channel negotiation failed

I'm trying to connect with MQ service and for ssl connection imported my certificate in cacerts file.
But when I'm running to connect, its showing below error
rc=2059 amq9503 channel negotiation failed
Do I need to create separate keystore and place my certificate in it, then point it using parameter javax.net.ssl.truststore ?
MQ error 2059 (MQRC_Q_MGR_NOT_AVAILABLE) has nothing to do with SSL.
This error means usually means you are missing a client-side SSL certificate.
# Check your certificate installation path
javax.net.ssl.keyStore=<path to key store containing just your client certificate>
javax.net.ssl.keyStorePassword=<password for that key store>

SunCertPathBuilderException: unable to find valid certification path to requested target`

I know there are a lot of question regarding the same thing on SO, but I don't seem to have found the solution for my problem yet.
I am using JavaMail API to connect to a mail server. Initially I used the port 110 to connect to pop3 server and that is when I got the following exception -
javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
I changed the port to 995 and got the following exception -
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Code for connecting to the server:
Properties props = System.getProperties();
props.setProperty("mail.store.protocol", "pop3");
props.setProperty("mail.pop3.ssl.enable", "true");
Session session = Session.getInstance(props, null);
store = session.getStore("pop3");
store.connect(ServerName, Port, UserName, Password);
Where am I going wrong? Any advice would be helpful. Thanks in advance.
The first error message says it all. You're attempting SSL to a plaintext port. Try disabling SSL, or use the correct port for SSL.
The second one says that your truststore doesn't trust the server certificate. If it's self-signed, you will need to import it into your truststore.

Applet Certificate CRL Distribution Point LDAP URL with no host

I'm developing a web application that uses applets signed with a certificate issued by the customer CA. That certificate contains an URL to a CRL Distribution Point that does not define the host and port. The certificate attributes "CRL Distribution Points" and "Authority Information Access" contain an URL similar to "ldap:///CN=my-cn...".
The log files generated by the Certificate Revocation-Checking API (C:\Users[my_user]\AppData\LocalLow\Sun\Java\Deployment\log) indicate that the values "localhost" and "389" are being used for host and port. This is a snippet of the log file:
...
certpath: DistributionPointFetcher.getCRLs: Checking CRLDPs for CN=xxx, O=yyy, L=zzz, C=PT
certpath: Trying to fetch CRL from DP ldap:///CN=_my-cn_?certificateRevocationList?base?objectClass=cRLDistributionPoint
certpath: CertStore URI:ldap:///CN=_my-cn_?certificateRevocationList?base?objectClass=cRLDistributionPoint
...
network: Connecting http://localhost:389/ with proxy=DIRECT
...
certpath: LDAPCertStore.engineInit about to throw InvalidAlgorithmParameterException
javax.naming.CommunicationException: localhost:389 [Root exception is java.net.ConnectException: Connection refused: connect]
at com.sun.jndi.ldap.Connection.<init>(Unknown Source)
...
Can anyone confirm that the host is mandatory, otherwise the default value "localhost" is used?
I read in the LDAP RFC (http://www.ietf.org/rfc/rfc4516.txt) that if the "host" field is not present, the client must have some a priori knowledge of an appropriate LDAP server to contact. Is it possible to configure the "host" attribute?
I'm using the JRE version 1.7.0_45 (build 1.7.0_45-b18).
Thank you,
Telmo Simões
Yes, the host is mandatory.
It depends a bit on how (if) your code creates the LDAPCertStore object. When you call the constructor, you need to pass an LDAPCertStoreParameters object to it. That class contains a contructor which allows you to specify the host (and port).
Try creating your LDAPCertStore like this:
LDAPCertStoreParameters params = new LDAPCertStoreParameters( "hostname", 389 );
CertStore store = CertStore.getInstance("LDAP", params);
Good luck.

JAVA - SSL - Client Certifcates

I've been developing a WS client using JAVA and I'm having a problem with SSL authentication. The WS are created on WCF and I have no access to the server, they work through HTTPS and uses a client certificate that needs to be installed on the client first. The server guys sent me a PFX certificate which I successfully installed on the OS (I'm using OS X) and I could then access the WS via a browser (Safari or FF are both that I tried which previously couldn't access the WSs).
I thought any app in the OS would use this certs but when I'm tried my JAVA app it didn't work; at first the following error was being thrown:
"javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"
I solved this by exporting the certificate to a CER file and using the keytool command line tool to add the certificate into the "cacerts" keyStore JAVA uses. But after this error went away the following started appearing: "403, forbidden". This is obviously because it's not using the SSL client cert for the site but I haven't been able to find a way to send it to it. Any help would be appreciated.
The following is the code I use to post to the WS:
URL url = new URL(p_url);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", contentType);
OutputStream out = conn.getOutputStream(); // on this line it shows the error
You can either create a specific SSLContext (using a KeyManager initialised with the keystore containing your client cert + private key), from which you derive an SSLSocketFactory, which you set into your HttpsURLConnection, or use the global settings.
You could set the following system properties (for the global settings):
javax.net.ssl.keyStore=path/to/keystore.pfx
javax.net.ssl.keyStoreType=PKCS12
javax.net.ssl.keyStorePassword=xxxxxxxxx
Alternatively, you can create your own KeyManagerFactory/KeyManager as described in this answer.
Since you've imported the server certificate in your cacerts, use null for the TrustManager[] argument of SSLContext.init() (it will pick up the default values).
In addition, since you're on OSX, you could use the KeychainStore directly. To do so, use ....keyStore=NONE, keyStoreType=KeychainStore and keyStorePassword=- (any password will do, since access to the key will be granted when you need it from the OS). I'm not sure if it works on Lion, though. Note that it may fail if you have more than one cert+private key in your store (see this issue).
Looks like you probably need to set up your own SSL SocketFactory,
http://vafer.org/blog/20061010073725/
I would think things have gotten better since 2006, so you may just need to specify a bunch of properties on the command line:
http://stackoverflow.com/questions/875467/java-client-certificates-over-https-ssl
You need to load the keystore they send you in your java application.
You can load it as a file from the file system in a Keystore object and use it. Read this example and especially the part about KeyManager i.e. createKeyManagers method.
Another option would be to load the keystore from windows. Read about Windows-MY provider

Ignore certificate errors when requesting a URL in Java

I'm trying to print a URL (without having a browser involved at all) but the URL is currently throwing the following:
javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException:
unable to find valid certification path to requested target
I'm calling the URL using a JEditorPane's setPage method, which just takes a URL as a parameter. Assuming I can't change anything server side and I still need to get to this resource, how would I go about ignoring the certificate error (or something else that gets me to my goal)?
Accessing this URL via a browser tells me the site is untrusted and asks me if I want to proceed.
Extend JEditorPane to override the getStream() method.
Inside that method, you can open a URLConnection. Test whether it is an HttpsURLConnection. If it is, initialize your own SSLContext with a custom X509TrustManager that doesn't perform any checks. Get the context's SSLSocketFactory and set it as the socket factory for the connection. Then return the InputStream from the connection.
This will defeat any attempts by the runtime to protect the user from a spoof site serving up malware. If that's really what you want…
This is possibly because your certificate in your keystore for accessing target HTTPS URL does not match the certificate from server.
You need to import the certificate in to your JVM's keystore.
Obtain keystore certificatesto access this URL and then importit into the main keystore with
keytool -importkeystore -srckeystore /path/to/custom/keystore -destkeystore $JAVA_HOME/jre/lib/security/cacerts
Assuming you are using Java from $JAVA_HOME
I would use erickson solution.
Another way is to add server's certificate (probably self signed) to your trusted certificates KeyStore (I would do it just in your test enviroment).
Use:
java InstallCert YourHost
to create a jssecacerts file
then copy it to the following folder:
$JAVA_HOME/jre/lib/security
If it is the same server that you will be contacting each time, it might be okay to simply trust this one certificate by adding it to the trust store. I wouldn't add this to the default cacerts file, but you can make your own keystore and specify it through the javax.net.ssl.trustStore system property.
Lastly, if you want to disable PKIX checking completely (which you should only do if you understand that this will compromise security quite a lot) the only way is to implement your own SSL context and trust manager as erickson has suggested.
On a related note: my mutual certifcate authentication with a WCF .net Web Service was causing issues in my test environment. I added these two lines to my code to fix the problem and allow me to work through the issue:
//Uncomment this in case server demands some unsafe operations
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
//this command allowed me to see the detailed debugger log for detecting the
//issue with my certs.
System.setProperty("javax.net.debug","all");
Here is a nice reference for dealing with SSL negotiation and certifcates in a Java/Windows/Unix world: Transport Layer Security (TLS) Renegotiation Issue Readme

Categories