I need to integrate an application with external web service which forces to use https. Authors of this web service provided me with .crt file which I should use for making https requests. After some investigation I've found the following code which uses KeyStore class for secured https access:
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream instream = new FileInputStream(new File(file));
try {
trustStore.load(instream, password.toCharArray());
} finally {
instream.close();
}
SSLContext sslcontext =
SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
SSLConnectionSocketFactory sslsf =
new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"}, null,
BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
HttpClients.custom().setSSLSocketFactory(sslsf).build();
In this code KeyStore requires input stream along with password trustStore.load(instream, password.toCharArray());. However, as I understand, we don't need password when using .crt file. So this way of loading certificate is not suitable for me. At the same time, from what I've discovered so far, the code I provided here is the only way to configure HttpClient for using SSL certificate. Is there any workaround to configure HttpClient for using .crt certificate?
Thanks,
Andrey
I assume the Web service is providing you with a self-signed certificate (i.e. not signed by a well-known CA). If it is already signed by a well-known CA that is in Java's cacerts file, then you don't need to do anything.
Otherwise, you have a couple of options:
Import the certificate into global cacerts keystore
Launch your application with application-specific keystore
In either case, you first need to convert crt file into jks keystore that Java uses. You can do this by:
$ keytool -import -keystore mykeystore.jks -storepass horsestaple
Note that keytool requires the password (horsestaple above) to be supplied for creating a jks store. You can put anything there; as you mention, public website certificates do not need password protection, they are public after all.
If you are doing option 1, make a backup of your cacerts and supply the cacerts file instead of mykeystore.jks. See the link below for the location of cacerts. For this option, you are all set, your application should be connecting to the Web service via HTTPS without any additional configuration, since Java loads cacerts by default.
If you are doing option 2, which is probably preferred at least for testing phase, you need to run your application with this parameter:
-Djavax.net.ssl.trustStore=mykeystore.jks
This is a JVM parameter, so supply it appropriately. This depends on how you are running you application.
Note that you will only have the imported certificate in this case, so your other HTTPS connections will not work. You can avoid this by first copying the standard cacerts to a temporary location, importing the key into it and using that in the command above. That will give you all the standard certificates, plus the one you need.
A slight downside of option 2 is that if new certificates are added or revoked, your application-specific keystore will not be updated. If this is a concern, you can merge keystores on the fly, for example:
Registering multiple keystores in JVM
In either case, you should now be able to just do a standard URL fetch, such as in example given here:
https://blogs.oracle.com/java-platform-group/entry/diagnosing_tls_ssl_and_https
i.e.:
final URL url = new URL("https://example.com");
try(final InputStream in = url.openStream()){
//…
}
More information here:
Keytool and general certificates info
https://docs.oracle.com/cd/E19830-01/819-4712/ablqw/index.html
Cacerts location
http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/keytool.html
More options for importing self-signed certificates
How to properly import a selfsigned certificate into Java keystore that is available to all Java applications by default?
Unless they provided you with their .crt file which should be used for validating connections to them, which would indicate they are using a self-signed certificate, they haven't provided you with anything useful. If you need a certificate as a client, the first thing you need is a public/private key pair, from which you generate a CSR, which you get signed. Nobody else can securely provide any of that except the signed certificate.
If they've provided their own (self-signed?) certificate, you need to load it into a truststore, not a keystore, via the keytool with the -trustcacerts option, and then tell Java to use that truststore, either via the javax.net.ssl.trustStore system property or by constructing your own TrustManager and feeding it to a custom SSLContext, as described in the JSSE Reference Guide.
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 am creating a Java program to get information from a server but I have to perform a ssl handshake with the server from the Java program.
I have myfilercert.cer file certificate for authentication purpose but I have no idea how I can load that certificate in java so that the java program can perform 'handshake' with the server where I want to get information from. Where to begin?
What you need is the java keystore. The keystore is a repository of security certificates used in SSL encryption.
You can read here about the Server Authentication During SSL Handshake. This is a keystore tutorial.
As an alternative to keytool, i would suggest a tool with a Graphical User Interface called Portecle. You can use it to browse the contents of your .cer file and see what's in it.
It can be useful to know about the various certificate encodings. Also read about the X.509 standard.
This is an article on java keytool essentials (which is the oracle tool that works with the java keystore).
You can google and find a lot of resources that instruct you how to generate. I think you will want to keep the certificate at the application level.
Some SO questions that helped me along the way:
Trust Store vs Key Store - creating with keytool - important to know the difference between the trust manager and keymanager
Java HTTPS client certificate authentication
How to export private key from a keystore of self-signed certificate
What is difference between cacerts and keystore
How to connect to a secure website using SSL in Java with a pkcs12 file?
Received fatal alert: handshake_failure through SSLHandshakeException
How to configure trustStore for javax.net.ssl.trustStore on windows?
Good luck!
You can use Apache HttpClient (or just use the required classes from it to use SslContextBuilder, really), and then it'd be like so:
SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
sslContextBuilder.loadTrustMaterial(new File("yourTrustStore.jks"), "thePassWord");
SSLContext sslContext = sslContextBuilder.build();
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) (new URL("https://thesite.com").openConnection());
httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
But you need to create a keystore for your certificate, which can be done with keytool. If you need this for android, you'll need SpongyCastle library, and use that as a provider for KeyTool to create a BKS keystore instead of a JKS keystore; and you will need to explicitly open the KeyStore in Java.
KeyStore keyStore = KeyStore.getInstance("BKS",
BouncyCastleProvider.PROVIDER_NAME);
byteArrayInputStream = new ByteArrayInputStream(keyStoreBytes);
keyStore.load(byteArrayInputStream, keyStorePassword);
Certificate[] certificates = keyStore.getCertificateChain("theCertAlias");
Certificate certificate = certificates[0];
This question already has answers here:
Truststore and Keystore Definitions
(7 answers)
Closed 6 years ago.
I have a PKCS#12 file which I considered as a keystore file since it contains one key entry & one certificate entry.
In Android, I see people programmatically install keystore in the following way (The code is from Android developer blog):
byte[] keystore = . . (read from a PKCS#12 keystore)
Intent installIntent = KeyChain.createInstallIntent();
installIntent.putExtra(KeyChain.EXTRA_PKCS12, keystore);
startActivityForResult(installIntent, INSTALL_KEYSTORE_CODE);
I also see people programmatically install only the certificate wrapped inside keystore:
Intent intent = KeyChain.createInstallIntent();
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, cert);
startActivity(intent);
Besides, I also see people install both the keystore & the certificate wrapped in keystore. For example, this article shows us how to first install keystore & then install the certificate wrapped in keystore programmatically.
I really get confused about when should I install keystore only & when should I install certificate (wrapped inside keystore) only ? And when should I install both ?? Could someone make me clear about this please?
For example, my keystore PKCS#12 file (mycert.p12) contains key/certificate pair, it is used to connect to VPN server. When should my android client install both keystore and certificate wrapped in the keystore ? When should client install only certificate wrapped in keystore? What are the differences ? I am quite confused about this.
I have a PKCS#12 file which I considered as a keystore file since it contains one key entry & one certificate entry.
Correct.
In Android, I see people programmatically install keystore in the following way ...
This is done when you have a keystore, i.e. a keypair and certificate.
I also see people programmatically install only the certificate wrapped inside keystore
This is done when you have someone else's certificate, typically a self-signed one, that isn't trusted by any of the default CA's (certificate authorities) that are already installed. You should never have to do this.
So note that you never do both with the same certificate, because the cases (the ownerships) are different. There can never be any doubt about which process is appropriate. If it's yours, import the keystore. If it's someone else's, import the certificate.
The ultimate normative reference for all this stuff is ITU Recommendation X.509.
Finally, some notes on the poor quality blog articles you have linked.
From Unifying key store access in ICS:
In the past, it was common practice for apps to maintain their own key
store if they needed to authenticate a secure SSL web server, or
authenticate the user to a server via a client certificate.
This is already incorrect.
To authenticate a web server you shouldn't need anything, if it has a CA-signed certificate. If it has a self-signed certificate you will need to import it into your truststore.
To authenticate yourself to a web server, you need a keystore containing your own private key and a certificate, preferably a CA-signed one. Otherwise the server has to import your self-signed certificate into its truststore, i.e. the converse of (1) above. Don't go down this path. Self-signed certificates are far more trouble than they are worth, which is nothing, as you can tell from the price you pay for them.
From Using ICS keychain API:
We first get the private key and certificate chain using the key alias
and then create and verify a signature to check if the key is actually
usable.
Complete nonsense. We already have the private key, the public key, and the certificate. They are already usable. Creating a signature and verifying it locally is just a complete waste of time.
Installing a CA certificate is not very different from installing a
PKCS#12 file: you load the certificate in a byte array and pass it as
an extra to the install intent.
The difference being that you use KeyChain.EXTRA_CERTIFICATE in the CA certificate case, and KeyChain.EXTRA_PKCS12 in the keystore case.
Since no one has answered you yet, I hope I can at least clarify some points from the blog article you have linked to.
In the past, it was common practice for apps to maintain their own key
store if they needed to authenticate a secure SSL web server, or
authenticate the user to a server via a client certificate.
These are the two basic use-cases right here:
If you are authenticating with a client certificate (which proves to the server that you are an authorized client), then you would only install a certificate.
If you are trying to verify the server's identity, then you will want to validate the server's certificate. In this case you will need a keystore installed (possibly a chain if you're not using self-signed certs). The private key in the keystore will be used to validate the server's certificate.
That second bit of code you have in your question was intended for creating a certificate chain (when you're NOT using self-signed certs):
We first get the private key and certificate chain using the key alias
and then create and verify a signature to check if the key is actually
usable. Since we are using a self-signed certificate the 'chain'
consists of a single entry, but for a certificate signed by a CA you
will need to find the actual end entity certificate in the returned
array.
Installing a CA certificate is not very different from installing a
PKCS#12 file: you load the certificate in a byte array and pass it as
an extra to the install intent.
Intent intent = KeyChain.createInstallIntent();
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, cert);
startActivity(intent);
I hope this explanation helps! :)
With respect to an android or any other 'client', that is, an application, the following hold -
A truststore (public key only) is required whenever it needs to validate the certificate (or a certificate chain) that is sent across by the server during SSL communication (in case of ssl communication the server will always present its certificate to the client).
If the server's certificate is already signed by a trusted certificate authority (implying that certificate is already present in the java-runtime-truststore that can usually be found under $JAVA_HOME/jre/lib/security/cacerts), then this step is not required unless a customized SSLContext is being used (which also means that a customized TrustManager is being used).
For example SO's current certificate is signed by DigiCert identified by SHA1-Thumbprint : 5F:B7:EE:06:33:E2:59:DB:AD:0C:4C:9A:E6:D3:8F:1A:61:C7:DC:25 and would likely be present in the 'cacerts' truststore under the alias 'digicerthighassuranceevrootca'. If a java client were to make a request to https://stackoverflow.com then by default there would not be any specific keystore or truststore required for communication.
A keystore (private and public key) is generally required when the client is required to digitally-sign some data which is being posting to the server.
A common example is xml-signing, you can find a mention here
It is also required if the server expects the client to present its own certificate for authentication as part of two-way ssl handshake. From what I have come across this is not common.
Links :
Two Way SSL
SO's own Keystore and truststore post
I have found the following post, which I think I may help because I need a way of verifying my website (that my java application connects to) without having to use the java keystore:
http://www.mombu.com/programming/java/t-ssl-for-java-without-keystores-1366416.html
However, I'm quite new to the world of SSL and don't know which files and passwords I need to use, so if anybody can point me in the right direction that would be great. If you didn't guess, I got my SSL certificate from StartSSL, who are ran/owned/something by StartCom Ltd. Here is a post I used for putting the details into the keystore:
https://forum.startcom.org/viewtopic.php?t=1390
Thanks in Advance!
Alternitively, is there any way for me to use a custom keystore. i.e I'll do what I need with the default keystore and then copy the keystore into the .JAR so that I can tell my application to use the one in the .JAR instead of the one in the Java install directory, etc...
Yes, you can create and use your own keystore for your app using the keytool utility which comes with the JDK.
Basically, a keystore is a datastore for your keys & certificates, which will be used in your app either to authenticate yourself to another entity or to digitally sign your messages or any other data.
A distinct keystore called trustore is used to keep the public key certificates of the entities you trust.
You can have these keystores placed in your classpath and specify the path in your app in code like (these are VM params which will affect all apps running in this VM):
Properties properties = System.getProperties();
properties.put("javax.net.ssl.keyStore", path/to/keystorefile);
properties.put("javax.net.ssl.keyStorePassword", keyStorePassword);
properties.put("javax.net.ssl.trustStore", path/to/truststore);
properties.put("javax.net.ssl.trustStorePassword", trustStorePassword);
EDIT:
Yes, you can specify the keystore & trustore locations and their passwords in a property file and load the properties file as myProperties.load(myProperties.getClass().getResourceAsStream("/path/to/proertyfile.properties")); and then use it in your code as (exception handling omitted)(this will not affect any other app):
KeyStore mykeystore = KeyStore.getInstance("JKS");
InputStream is = new FileInputStream("/path/from/myproperties");
mykeystore.load(is, myKeystorePasswordFromProperties.toCharArray());
I got a certificate .cer and I'd like a script to import it in the Trusted Publisher list of certificate.
I managed to do this thing in C#
X509Certificate2 certificate = new X509Certificate2(filePath.Text, "Telecomitalia1?12524", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
X509Store store = new X509Store(StoreName.TrustedPublisher);
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
store.Close();
Is there a way to do the same in Java?
Thanks a lot.
You can do the equivalent in Java. Check out the MSCAPI provider.
Provides direct read-write access to MS Window's keystores. The Windows-MY keystore contains the user's private keys and the associated certificate chains. The Windows-ROOT keystore contains all root CA certificates trusted by the machine.
KeyStore ks = KeyStore.getInstance("Windows-ROOT");
// Note: When a security manager is installed,
// the following call requires SecurityPermission
// "authProvider.SunMSCAPI".
ks.load(null, null);
ks.setCertificateEntry("alias", cert);
ks.store(null, null); //again the permissions here...
The way java handles certificates is very different to how Windows handles certificates. Windows provides predefined stores for certificates for the machine account, user accounts and service accounts, and distinguishes certificates based on their purpose (e.g. personal vs trussted CA.)
Java simply provides the concept of the Keystore - an arbitrary file of your choosing where certificates are stored. There is a default keystore used by the JDK for trusted CAs, located at$JAVA_HOME/jre/lib/security/cacerts.
To import the certificate into a keystore, you can use the keytool that is shipped with the JDK - which keystore you use depends upon your application.
See
KeyTool
Most common keytool commands
Using the keytool, like the Java tutorial explains so well.
If you want to do it from code (not with the keytool utility) then this article will get you started.