SunCertPathBuilderException despite setting trusr store properties - java

I need to consume some HTTPS web services. To use the certificate, I'm setting (javax.net.ssl) trustStore, trustStorePassword, keyStore and keyStorePassword properties through the System.setProperty.
If I do new URL([HTTPS...WSDL]).openStream(), it works. So, I'm sure that trust and keystore properties are ok (without setting those properties, my routine failed).
The problem is when I try to consume the service, I'm getting the famous
sun.security.provider.certpath.SunCertPathBuilderException: unable to
find valid certification path to requested target
exception.
Anyone have an idea of what I need to do?
Thanks a lot.

There are three possible failures:
You are using a self-signed certificate.
Your certificate has been signed by a common CA (Certificate Authority) using an intermediate certificate, but their intermediate certificate is not served by your web server.
Both the server certificate and the CA intermediate certificate are served by your web server, but the Java truststore does not contain the root certificate.
To solve the last step use:
Import CA root cert

Related

Java SSL Certificate Verification with a proxy

I have an Issue with the verification of a ssl certificate.
What I am trying to do, is sending some data from a java program to a server, which then stores that data.
The issue is, that the ssl certificate validation fails with the following exception:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed [...] unable to find valid certification path to...
I am able to resolve this issue, by adding the "end-user" certificate of the server to the truststore I am using.
The chain looks something like this:
Root Certificate 1
Intermediate Cert 11
Intermediate Certificate x
End-User Certificate
Something to note about this whole chain / process:
The communication is using the companies proxy, which replaces all the certificates, and creates its own chain.
So back to the issue:
By adding the end-user certificate everything runs fine. But only for a little while, before that certificate gets refreshed and the one I added is no longer valid. I have tried adding just the root certificate, just each intermediate certificate, adding all 3 certificates and also adding the certificate which would be used if the proxy does not replace the chain. But somehow the certificate cannot be validated.
Is there something I might be overlooking? Do I have to add something else to be able to validate the certificate?
Edit:
Maybe something to note:
I checked the chain, by using the browser and navigating to said server, and then checking the ssl certificates.

sun.security.validator.ValidatorException: SunCertPathBuilderException -while importing certificate

I am getting below Exception
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
I have set the SSL certificate in the location
C:\Program Files\AdoptOpenJDK\jdk-11.0.9.11-hotspot\lib\security
keytool -import -keystore cacerts -file C:\Users\test\Desktop\Certificate\oCertificate.cer
But i am getting the above exception while i am hitting the server.
Results i saw
I have added the certificate to the Jdk cacerts file but then it worked for two days than again i was getting the same error. I am unable to get it was working i am able to succesfully ping the server than again it is showing the exception.
Is the problem you describe that running keytool to import the certificat gives you this error? Please provide the option -trustcacerts and see the documentation about this:
Import a New Trusted Certificate
Before you add the certificate to the keystore, the keytool command
verifies it by attempting to construct a chain of trust from that
certificate to a self-signed certificate (belonging to a root CA),
using trusted certificates that are already available in the keystore.
If the -trustcacerts option was specified, then additional
certificates are considered for the chain of trust, namely the
certificates in a file named cacerts.
If the keytool command fails to establish a trust path from the
certificate to be imported up to a self-signed certificate (either
from the keystore or the cacerts file), then the certificate
information is printed, and the user is prompted to verify it by
comparing the displayed certificate fingerprints with the fingerprints
obtained from some other (trusted) source of information, which might
be the certificate owner. Be very careful to ensure the certificate is
valid before importing it as a trusted certificate. The user then has
the option of stopping the import operation. If the -noprompt option
is specified, then there is no interaction with the user.
Source: https://docs.oracle.com/en/java/javase/11/tools/keytool.html
Alternatively you may find that keytool is not very user-friendly and you may enjoy other software like: https://keystore-explorer.org/downloads.html more.
Or if the problem is that your (TLS-client, or even TLS-server) software has some certificate issue it might be as jccampanero already suggested that the server might have switched to a different certificate, or for all I know the server may actually be several different servers behind a load-balancer which may not all have the same certificates. (Or maybe you installed some Java update that replaced the default cacerts file?)
In case of problems I highly recommend reading the JSSE-documentation and enabling debug logging with java option -Djavax.net.debug=all or maybe a little less than all like handshake see the Java 11 docs at:
https://docs.oracle.com/en/java/javase/11/security/java-secure-socket-extension-jsse-reference-guide.html#GUID-31B7E142-B874-46E9-8DD0-4E18EC0EB2CF
This shows the exact TrustStore your application uses, the certificate(s) that the server offers during the handshake and a lot of other negotiation stuff that is part of the TLS handshake.
If you prefer full control of who you trust to issue certificates you can configure your own truststore instead of the default that can live outside your Java installation with options like:
java -Djavax.net.ssl.trustStore=samplecacerts \
-Djavax.net.ssl.trustStorePassword=changeit \
Application
I trust that studying this debug logging should make it straightforward to resolve the issue, if it doesn't please provide us with some of the relevant logging.
The error you reported indicates that your application is unable to establish a trusted SSL connection with the remote peer, because it is unable to find a valid certification path.
Which seems very strange to me is why it worked a few days ago and now it is not: perhaps the server changes the certificate, or maybe your setup change in some way.
The SSL configuration will be highly dependent on the software you are using to connect with the remote server: it can be different if you are using standard Java classes like URLConnection or HttpURLConnection, or libraries like Apache HttpClient or OkHttp, among others. The difference mainly has to do with if that piece of software uses or not Java Secure Socket Extension (JSSE) under the hood.
Assuming that you are using JSSE, in order to successfully configure your trust relationship, you need to properly configure a TrustManager, and more specifically, an X509TrustManager. From the docs:
The primary responsibility of the TrustManager is to determine whether the presented authentication credentials should be trusted.
Basically you can configure this X509TrustManager in two ways.
On on hand, you can create your own implementation. For example:
// This KeyStore contains the different certificates for your server
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(
new FileInputStream("/path/to/serverpublic.keystore"),
"yourserverpublickeystorepassword".toCharArray()
);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); // SunX509
tmf.init(keyStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
SSLContext sslContext = SSLContext.getInstance("TLS");
// You can configure your client for mutual authentication
// and/or provide a SecureRandom also if you wish
sslContext.init(null, trustManagers, null);
Please, consider read this SO question for a complete example.
Or, on the other hand, as you are doing, you can configure the standard TrustManagerFactory properly.
As indicated in the above-mentioned documentation, the standard TrustManagerFactory uses the following process to try to find trust material, in the specified order:
First, you can use the javax.net.ssl.trustStore system property to point to the keystone that contains your trusted server certificates when running the application. If the javax.net.ssl.trustStorePassword system property is also defined, then its value is used to check the integrity of the data in the truststore before opening it.
If the javax.net.ssl.trustStore system property was not specified, then:
if the file java-home/lib/security/jssecacerts exists, that file is used;
if the file java-home/lib/security/cacerts exists, that file is used;
if neither of these files exists, then the SSL cipher suite is anonymous, does not perform any authentication, and thus does not need a truststore.
No matter the chosen mechanism used, you must be sure that the keystore contains all the necessary certificates to trust the remote server, not only the SSL certificate, but all the certificates in the certificate chain.
openssl provides an useful command that allows you to obtain all the certificates used in the SSL connection:
openssl s_client -showcerts -connect google.com:443
Of course, modify the domain as appropriate.
It will output different certificates; be sure to save each of them, including —–BEGIN CERTIFICATE—– and —–END CERTIFICATE—–, and include them in your keystore.
This handy utility can probably be of help for this purpose as well: as indicated in the project README, it basically will try to connect to the remote peer and save the necessary certificate information. This related article provides more information about the tool.
As other answers have pointed out the list of things that need to be setup correctly is long and varied depending on which HTTP client you are using, but assuming you have followed the information provided in the other answers, here's what I would check:
Make sure the cert was imported correctly into the cacerts file. This can get get overwritten by software updates, Group Policy (if you are on a windows domain), you accidentally changed JVMs, and so on. Double check the path to the JVM that is in use
Importing a certificate isn't always enough to ensure the trust is correctly setup. The hostname used to access the service must match the imported certificate either by the Common Name (CN), or a Subject Alternate Name (SAN DNS) entry. This first image shows the Common Name from the current google cert.
, the second image shows the SANs:
The upshot of this is that whatever hostname you are using to access the service (eg test.amazingapp.com) must match either the CN or one of the entries in the SAN field. If you are accessing the service via an IP address, then the IP needs to be in either of these fields.
Finally, ensure the certificate is not expired.
If you've checked all these and it still isn't working, then you will likely need to turn on SSL logging with the system property javax.net.debug as described here on the Oracle JSSE Site, using $ java -Djavax.net.debug=all will give all the information that exists about the handshake. It will take some time to work through but the answer will be there.

How to add CA certificate to cacerts store so that it works as expected?

UPDATE: After I have found a solution, I edited the question to be more clear for future reference.
I've got a corporate (ie not well known) CA certificate from a company which provides us a web services to be called from Java. I added this CA certificate to default cacerts trust store (keytool -import -file cert.cer -alias myca -keystore jre/lib/security/cacerts), but connection to the service still fails with the dreaded "PKIX path building failed" message. I have checked that issuer field of the end server certificate is the same as in the CA certificate and also validity dates is ok.
I don't know how to explain this. I can think of the following reasons but I don't know which one is true:
I have noticed that when I add also the end server certificate to trust store, the connection is OK. Maybe cacerts by design don't work as I expect (ie all the certificates signed by an authority added there are considered valid), but instead I have to add all the end server certificates to a trust store including CA certificate of their issuer.
I have to add CA certificate in some other way - by different command, to different file etc.
Maybe the CA certificate is not correct and keytool refuses to consider it a certificate authority.
Maybe PKIX path building fails for other reason.
How can I debug this problem more to find an answer?
Details:
The end server certificate is wildcard certificate
There is no intermediate certificate, just root and the end certificate
I was facing the same problem with "PKIX path building failed" with Let's Encrypt signed certificates at a time Java didn't incorporate the Let's encrypt CA certificate in its default trust store.
My story is written in detail here: http://blog.novoj.net/2016/02/29/how-to-make-apache-httpclient-trust-lets-encrypt-certificate-authority/
At the end I was able to make Java trust "the end of the chain" server certificate by creating internal trust store embedded in my application that contains only root CA certificate (and the backup one).
I much more prefer creating internal application truststore than importing certificate in main Java trust store for two reasons:
you don't need another extra step in install procedure for initializing the global trust store
you limit "the trust" to you application and don't affect another applications running on the same JVM (or better you can even limit the trust to the certain instances of client objects in your application if required)
Maybe I had a different scenario than you're facing, so downvote me if I didn't get the point.
The trust store needs to contain the root certificate (the CA's cert).
I'm not sure if that's what you mean by "the last one I'm the chain", but the CA certificate should be the last one in the certificate chain presented by the server.
If your certificate is signed by a well-known CA, then the CA cert should be in the trust store, and if the server's certificate chain is set up properly, everything should just work.
If yours is a self-signed certificate, then the root certificate will not be in the trust store, and you will have to add it.
Thanks to #pedrofb comment I found out that the reason PKIX path fails is simply that the CA certificate I got is not the CA that signed the end certificate. What made it so complicated is the monstrous incompetence of company that gave me the CA certificate which obviously has two CAs with almost the same description (cn, o, st, c) which differs only in SN and which both issued the same wildcard certificate. Only after I became super paranoid and compared the SNs, I understood the problem.

SSL certificate validation using Java client

I am writing a micro service which talks to another service through HTTPS. In java client implementation, reading root certificate from truststore and then validate incoming certificate through that.
Now, my question is - by default only 2nd level certificate will be validated using root Cert or all chained certificates in turn will also get validated?
I am Using okhttp; and my assumption is that all Java clients should behave in same way, please let me know if there is any subjectivity here? Thanks.
If you include the root certificate in the truststore, the entire chain will be validated
The SSL server during handshake sends to client the certification chain from leaf certificate to root. The root certificate may be included but usually it is not
The default Trust Manager of a Java client will validate the provided certification chain looking in the truststore until it find the issuer of the certificate or the certificate itself

Determining which certificate to use in two way ssl

I'm having an issue sending our certificate (client) to another server during a web service call.
We're expected to return the certificate with the CN:
b2b-test
however whenever we receive the servers certificate and attempt to send our own, we're getting:
Unable to find valid certificate path to requested target
From my understanding, if I put our certificate b2b-test inside of our keystore jre/lib/security/cacerts, then when receiving the server request, it should send the appropriate certificate.
How does the client know which key from the keystore it should send to the server? Should I be specifying the exact certificate to send during my request, or is it enough for it to be in the keystore?
Currently the server is sending back the certificate
b2b-a
The certificate chain shows:
*** Certificate chain
chain [0] = [
[
Version: V3
Subject: CN=b2b-a, OU=Web Technologies
The URL for the service is:
https://b2b-a/service
If I configure it like one way ssl, and put the servers certificate into our cacerts, then it picks up their cert and fails the handshake (I'm assuming because it's expecting our cert and not theirs back due to it being a two way setup).
I'm a little stumped here. Any clarification on what I've said is greatly appreciated.
The "Unable to find valid certificate path to requested target" error usually occurs when one side passes a certificate for which the other side doesn't trust any of the certs in the chain. You'll generally (always?) see the error message on the side that's receiving the cert (the side that's missing the truststore).
If you're the client and they're the server, one-way SSL would send their cert from them to you and you would validate it (making sure that the hostname matches the content in the cert), but it would not send any cert from you to them nor would they do any validation.
You'll want to make sure you're configuring a truststore that contains either the b2b-a certificate or a certificate that was used to sign it (but that cert doesn't appear to have any root CAs, so you're stuck with directly importing the cert itself). So yes, you want to do what you wrote in your post and put their certificate into your cacerts. And you'll also need to make sure that the b2b-a service trusts the cert you're sending (or a root CA that was used to sign your cert, if any), so you'll need to put your cert into their cacerts as well, or do something equivalent.
Note that putting a cert in cacerts (or in a truststore JKS) DOES NOT send any certs to anyone; any truststore is used only to allow you to validate that you trust the certificate someone else provides you, either because you trust the cert or because you trust a CA cert that was used to sign the cert. No certs are picked out of the cacerts directory to send to another machine.
If you're using a JKS rather than the JRE directory (which is generally a better idea, since you can specify a different set of trusted certs for your process without changing the default set for anyone running a Java project within your JRE), it's possible to use the same JKS as both a keystore and a truststore (you'll just provide the same filename and password for both the keystore and the truststore properties), or you can have two separate files; either approach will work.
From my understanding, if I put our certificate b2b-test inside of our keystore jre/lib/security/cacerts, then when receiving the server request, it should send the appropriate certificate.
Certainly not. That's a truststore. A source of trusted certificates. Don't mess with it. It isn't used for the purpose you require and in any case it is updated every Java dot release, which clobbers your changes.
Your own certificate and private/public key pair should go in a file of your own, a keystore, which is defined to JSSE by the javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword system properties, or by more complex means described in the JSSE Reference Guide.
The keystore is a precious and confidential file containing your private key. Guard and protect it. Private key leakage would compromise your security and identity.

Categories

Resources