The server has changed from http to https. Instead of modifying client code (of course the host which was http is now https), we use keytool to save server CA. When I used keytool, I see a list of all stored CA (about 100 pieces).
So how does JRE know which CA to use? And what is the story behind this, like why when JRE is configured with a CA the client code could remain the same?
Thanks.
I'm trying to answer the basic questions you pose.
Java and Keytool know which CAs to trust by consulting a special file named cacerts which is shipped with the JRE and it's placed in <JRE_HOME>/lib/security.
This kind of file is usually called KeyStore or TrustStore.
This file is shipped by default by the "creator" of the JRE (e.g. Oracle) and contains list of Certificate Authority certificates to be trusted (usually updated at every release).
It's not that different from the same process for browsers.
As for the story behind this, if I understand correctly, we are talking about PKI (Public Key Infrastructure) which, in you case, very roughly, means that when your JRE keystore gets updated with the new CA certificate, then entities (such as SSL Sites, applets,etc) presenting a certificate provided (usually sold) by that CA are now basically valid and trusted, since now a trusted third party (the new CA) can guarantee (by means of encryption and digital signature) that "you are effectively you" in a digital scenario.
Also worth noting that, Keytool can be instructed to use a user-defined keystore with the -keystore <keystore_path> command line option (and even the JVM can configure a specific non-default truststore for SSL connection via a system property like this)
Related
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.
I would like to understand the Java runtime's requirement for SSL certificates storage in general.
I understand it can be copied to the host's /etc/ssl/certs folder but for Java, does it need to import to a specific Keystore for a runtime to be able to use and consume in any SSL verification process by the application?
E.g.
If I have a JRE client that requires packaging of a root/intermediate certificates to make web client internally to site1.foo.com, I will need the root and intermediate certificates dependent on the chain to verify the request.
With various other runtime environments, it seems I can just place them in the /etc/ssl/certs folder:
NodeJS => How to add custom certificate authority (CA) to nodejs
Go => Where is Golang picking up root CAs from?
However, presumably for usage in Java, I need to go an extra step and use keytool and import into a specific Keystore?
Presumably, it can't just pick up from a common directory as per above?
Hope my question makes sense.
In Java, collections of certificates are usually accessed through a KeyStore interface.
As remarked in the comments the default SSLContext will read the certificates from a PKCS12 (or JKS) file located in $JRE_HOME/lib/security/cacerts.
However that is not the only possibility and you don't have to call keytool to add trusted certificates:
on Debianoids you can use -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts (cf. this question) to use the the PKCS12 file provided by the ca-certificates-java package. It is updated whenever you call update-ca-certificates. Therefore you just need to add a *.crt file in /usr/local/share/ca-certificates and run update-ca-certificates.
if you don't use the default SSLContext you can init it with a different TrustManager (cf. this question). That's how Tomcat 8.5+ loads certificates in PEM format.
Unfortunately there is no implementation of KeyStore that reads certificates from a directory, but that can easily be written.
Edit: On Debianoids the packaged JREs already use /etc/ssl/java/cacerts, so no further configuration is needed.
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.
I have a small javafx2 application that is used in the office. Some guys run it from Firefox, some run it as a desktop app. Webstart is great.
The current approach is that I have added this to the jnlp file:
<security>
<all-permissions/>
</security>
Then created a key in a keystore using the keytool:
keytool -genkey -keystore yourKeystore -alias keyname
And I sign all jar files that are to be on the user's PC using jarsigner:
jarsigner -keystore ./myapp.keystore -storepass xxx -keypass yyy <path to jar file> keyname
So the user either starts the app using the shortcut file (the jnlp file) on their desktop, or they browse to the app in Firefox. This works great, except nowadays we get this dialog that says:
Running applications by UNKNOWN publishers will be blocked in a future
release
and I am worried about what will happen when that future release is out.
I do not have a deep understanding about all this code signing thing. I know that it works by embedding some binary data into files that is used to prove that the releaser of the app is a company/individual that is recognized by the Certificate Authority (and this is what jarsigner does AFAIK). Certificates are recognized by OSes like Windows and Linux, by webbrowsers like Firefox and Safari and Java also recognizes certificates at 2 levels, user and system. There are widely accepted Certificate Authorities (like Verisign) that are recognized by default in OSes like Windows and Linux. I know that webbrowsers also recognize some Certificate Authorities, but I think the list they recognize can be different from the list the OS recognizes. I guess Java also recognizes some but I do not know what. I also know that I can have system level configured keystores in Java by specifying the keystore in ~/.java/deployment/deployment.properties, deployment.system.security.trusted.certs. If I do not want to pay for a certificate, I can create my own (this is what I was doing) so my certificate is from an UNKNOWN publisher. What I was hoping for that if I specify my keystore via deployment.system.security.trusted.certs then it will solve this problem for the office but apparently it does not, which I do not understand because in this case the system administrator says that he recognizes this certificate. So now I am thinking about making a certificate using makecert in windows which can then be pushed down to client PCs via GPO. My understanding is that this will create certificates that are recognized by the OS, but I do not know if makecert made certificates would be treated the same way as e.g. verisign certificates in Windows, in Firefox and also in Java. And I do not know if UNKNOWN refers to a list within java or it refers to the fact that the Certificate Authority is not recognized by the OS.
My questions:
If someone could fix the mistakes I made in the above description I would appreciate it. I believe I am misunderstanding something, but I do not know what.
I just cannot believe that Webstart won't work unless we pay for a certificate. I guess what we need is a mechanism that a sysadmin can say that he wants the office PCs to recognize a given certificate. Does anyone have an idea how to do this in the future?
If we have to purchase a certificate, could someone please let me know what to watch out for or how that works in general? Can I use jarsigner the same way?
If we have to purchase a certificate, is there a list of Certificate Authorities out there that we can pick from? Obviously I want to go for the cheapest as I find this an administrative overhead.
Thank you for your help.
My setup that works with JRE7u40 and JRE8 is this:
I have a self-signed cert for my certificate authority (CA). This cert must be trusted by the system (e.g. add it to Java Control Panel / Security / Certificates / Signer CA, but there may be other ways to make your CA trusted).
Then I have a code publisher certificate, signed by my CA. I use this cert to sign all my code (jars).
Furthermore, if you want all permissions for your code, I suggest these MANIFEST.MF attributes as QDH minimum (since JRE7u40, if I remember):
Permissions: all-permissions
Codebase: *
Trusted-Library: true
Trusted-Only: true
First time you run such application, you will still see the security warning because runtime is unable to ensure that code signer certificate was not revoked by its CA.
However, now you have the option to permanently accept this publisher (code signer certificate), and if you do, the code signer certificate will be added to trusted publishers (Java Control Panel / Security / Certificates / Trusted Certificates) and you won't see such warning again.
If you want to avoid this step, I guess you have to setup your CA infrastructure properly to support certificate revocation verification. I assume that includes some extra attributes in your CA certificate and availability of special certificate revocation service. For intranet deployment, you may skip that.
I've got a java applet that loads some pre-installed native code extensions to display custom content in the browser. Some of this content may include native code to be loaded by the JVM. Obviously, this is a security concern. I'd like to enforce that all content comes only from authorized servers.
The path I've been following to accomplish this is to create a keystore that contains just one SSL certificate. I set the keystore location and password and turned on debug output.
System.setProperty("javax.net.ssl.keyStore", "C:\\keys\\keystore");
System.setProperty("javax.net.ssl.keyStorePassword", "changeit");
System.setProperty("javax.net.debug", "ssl");
I was under the impression that this would mean that the JVM would have access to only the one keystore file and consequently the one key inside it. In fact, the SSL debug info lists something like 75 CA keys in addition to the one key I added. Clearly, this isn't going to keep anyone from sending us untrusted code.
Is there a way to tell the SSL system to only use a single certificate? Should I be using a completely different approach?
Update:
Changing the cacerts file isn't really an option here. The JVM should continue to function normally for other applications using it. Is there a way, at runtime, to elect not to load that file? I'm looking at the TrustManager and KeyManager classes but I don't really understand how to use them.
You need to set the javax.net.ssl.trustStore system property to point to a keystore with your one certificate in it. The keystore is for your authentication credentials, not your peer's.
The very question is wrong here. A certificate is only a proof of identity. You don't authorize certificates, you authorize identities. What if the same client comes up with a new certificate?
The correct answer is to install a HandshakeCompletedListener that checks the peer identity. The truststore is only there for authentication, i.e. is that person who they said they were. What you are doing is authorization, which is a different thing completely. You shouldn't use the truststore (or any PKI mechanism) for authorization.
You also have the global keystore installed with the JRE, which is where all the CA's are stored. Try to rename it and see what happens.
You still "see" the CA certificates of the system-wide JRE cacerts file located in java.home/jre/lib/security, which is normal.
Now, quoting the keytool documentation about this file:
IMPORTANT: Verify Your cacerts File
Since you trust the CAs in
the cacerts file as entities for
signing and issuing certificates to
other entities, you must manage the
cacerts file carefully. The cacerts
file should contain only certificates
of the CAs you trust. It is your
responsibility to verify the trusted
root CA certificates bundled in the
cacerts file and make your own trust
decisions. To remove an untrusted CA
certificate from the cacerts file, use
the delete option of the keytool
command. You can find the cacerts file
in the JRE installation directory.
Contact your system administrator if
you do not have permission to edit
this file.
In your case, it might be easier to entirely replace the cacerts with your own key store instead of removing all 75 entries if this is really what you want. If not, then you should use a different approach indeed (why don't you just restrict or hard code the list of authorized servers?).