on 01-27-2015 9:15 AM
Hi,
when trying to access HTTPS destinations using Java Tomcat I'm getting errors from
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 guessing that this is because I don't have a certificate loaded for the site I am trying to connect to.
However, as I'm using Tomcat, I'm not using the connectivity service (other than to store the URL of the destination). How do I go about loading a certificate for this connection?
Of course running locally the Tomcat instance happily negotiates the SSL connection since my local java truststore has the main trusted CA certs.
I can, of course, generate a new truststore, add the certificate of my destination in there, but how do I specify the password that should be used to access the truststore/or that it should be used at all?
If it's not possible to add certs to the Tomcat runtime, it would be nice if the major trusted CAs were loaded by default.
I however, have the solution happily negotiating to an XS service on HCP using HTTPS.
Thanks for some help!
Chris
OK for the record - here is the solution that worked:
in my servlet
ArrayList<String> myTrustedCerts = new ArrayList<String>();
myTrustedCerts.add(request.getServletContext().getRealPath("/Certs/linkedin.cer"));
myTrustedCerts.add(request.getServletContext().getRealPath("/Certs/digicertGlobalRootCA.cer"));
myTrustedCerts.add(request.getServletContext().getRealPath("/Certs/digicert2.cer"));
TrustManager[] trustLinkedIn = new TrustManager[] { new MyTrustManager(myTrustedCerts) };
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustLinkedIn, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (GeneralSecurityException e) {
logger.error("problem faking LI cert lookup", e);
}
and then my trust manager
public class MyTrustManager implements X509TrustManager {
private static final Logger logger = LoggerFactory.getLogger(MyTrustManager.class);
private ArrayList<X509Certificate> certs;
private X509TrustManager defaultTm;
public MyTrustManager(List<String> certLocations) {
super();
defaultTm = null;
TrustManagerFactory tmf;
try {
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// Using null here initialises the TMF with the default trust store.
tmf.init((KeyStore) null);
// Get hold of the default trust manager
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
defaultTm = (X509TrustManager) tm;
break;
}
}
certs = new ArrayList<X509Certificate>();
InputStream inStream;
for (String certLocation : certLocations) {
inStream = new FileInputStream(certLocation);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
inStream.close();
certs.add(cert);
}
} catch (NoSuchAlgorithmException | KeyStoreException | IOException | CertificateException e) {
logger.error("issues with additional trust manager", e);
}
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
defaultTm.checkClientTrusted(chain, authType);
} catch (CertificateException e) {
for (X509Certificate cert : chain) {
boolean certNotFound = true;
for (X509Certificate knownCert : certs) {
if (cert.getPublicKey().equals(knownCert.getPublicKey())) {
certNotFound = false;
break;
}
if (certNotFound) {
throw new CertificateException();
}
}
}
}
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
defaultTm.checkClientTrusted(chain, authType);
} catch (CertificateException e) {
for (X509Certificate cert : chain) {
boolean certNotFound = true;
for (X509Certificate knownCert : certs) {
if (cert.getPublicKey().equals(knownCert.getPublicKey())) {
certNotFound = false;
}
}
if (certNotFound) {
throw new CertificateException();
}
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] certArray = new X509Certificate[128];
if (certs.size() == 3) {
certs.addAll(Arrays.asList(defaultTm.getAcceptedIssuers()));
}
return certs.toArray(certArray);
}
}
It works. It will only allow calls to the server that identifies itself with the same certificates that I have loaded. I'm guessing there are better ways that I could have done this if I'd just verified the root cert, but it would have been a darn sight harder.
I'll have to watch out for those certificates expiring, but hopefully before then a way to add root CAs to a HCP Java Web Tomcat servers will arrive
Cheers,
Chris
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Chris,
You could use the procedure described in [1] to upload your custom keystore to the cloud. Then you could use it in your app, e.g. as in [2].
Regards,
Lazar
So - got around it by doing this:
public class MyTrustManager implements X509TrustManager {
public MyTrustManager(String certLocation) {
super();
this.certLocation = certLocation;
}
private String certLocation;
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] certArray = new X509Certificate[1];
ArrayList<X509Certificate> certs = new ArrayList<X509Certificate>();
InputStream inStream;
try {
inStream = new FileInputStream(certLocation);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
inStream.close();
certs.add(cert);
} catch (IOException | CertificateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return certs.toArray(certArray);
}
}
and just before making the HTTPS call:
TrustManager[] trustLinkedIn = new TrustManager[] { new MyTrustManager(request.getServletContext().getRealPath("/Certs/linkedIn.cer"))
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustLinkedIn, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (GeneralSecurityException e) {
logger.error("problem faking LI cert lookup", e);
}
So now outbound connections to the linkedIn API server are being accepted, but it's still not ideal. I will try to improve it, but it's going to have to do unless the normal root CA certs are available somehow.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Probably worth noting that in past I've used non-Tomcat runtimes and have had no problems connecting to the same destination - and I haven't used the destination API, or loaded certificates, it seems the standard root CA's were loaded by default. But not in this case. (apparently - happy to be wrong!)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
87 | |
10 | |
10 | |
10 | |
7 | |
6 | |
6 | |
5 | |
5 | |
4 |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.