How to use the Windows Keystore (MCS) with JDBC? - java

I am trying to create a java application that uses PKI for authentication. I need to be able to retrieve a certificate from the Microsoft Certificate Store (MCS) and pass it along to an Oracle database (11.2).
I am connecting using the jdbc:oracle:thin driver. After spending quite some time on google I have come up empty.
I've found different properties to change (depending on the article):
set the property javax.net.ssl.keyStoreType = "Windows-MY"
set the javax.net.ssl.keyStore = "Windows-MY"
javax.net.ssl.keyStore should be set to "None" (if using a custom KeyManager which I do not believe will work since by the time it gets into my custom KeyManager I will already be given the certs from a keystore specified in the connection properties).
Of course all of these people are claiming success, but nothing has worked for me. I have tried every example I have been able to find all with no luck. I was able to successfully authenticate when I was using Oracle wallets so I know my certificates are fine. If anyone has done this before and is willing to post some code that would be great.
I know most people are using the Windows keystore with a website and therefore are creating their own SSLContext, but I cannot imagine I am the only one who has wanted to do this using JDBC (which as far as I know does not allow me to provide it an SSLContext).
This is the code that I believe should work, but does not.
DriverManager.registerDriver)new OracleDriver());
String url = "jdbc:oracle:thin:#(DESCRIPTION=(ADDRESS=(PROTOCOL=TCPS)(HOST=host)(PORT=2484))(CONNECT_DATA=(SERVICE_NAME=someName))(SECURITY= (SSL_SERVER_CERT_DN=\"CN=TESTSERVER\")))";
java.util.Properties props = new java.util.Properties();
props.setProperty("javax.net.ssl.keyStoreType", "Windows-MY");
props.setProperty("javax.net.ssl.keyStore", "NONE");
props.setProperty("javax.net.ssl.trustStoreType", "Windows-ROOT");
props.setProperty("javax.net.ssl.trustStore", "NONE");
props.setProperty("oracle.net.ssl_server_dn_match", "true");
props.setProperty("oracle.net.authentication_services", "(TCPS)");
Connection conn = DriverManager.getConnection(url, props);
This code fails with the exception:
java.sql.SQLRecoverableException: IOException: The Network Adapter could not establish the connection

This article should give your more details. Although it doesn't use the system properties, Windows-MY is clearly a store type and it's not file based. Therefore, javax.net.ssl.keyStoreType should be Windows-MY and javax.net.ssl.keyStore should be set to NONE (upper case may matter), see the JSSE Ref Guide (Customization):
javax.net.ssl.keyStore system property
Note that the value NONE may be specified. This setting is appropriate if the keystore is not
file-based (for example, it resides in a hardware token).
You may also need to configure your trust store in a similar way if your server certificate isn't trusted by your default Java trust store.

Related

SSL connection error when connecting to MySQL, using JDBC

The problem that we are facing is well documented in https://stackoverflow.com/questions/34189756/warning-about-ssl-connection-when-connecting-to-mysql-database.
We started facing this issue upon transitioning from MySQL 5.6.51 to MySQL 8.0.27.
The fix that is suggested works for us but we have an issue in that we don’t want to update the Java source files to change, for example, from
jdbc:mysql://localhost:3306/Peoples
to
jdbc:mysql://localhost:3306/Peoples?autoReconnect=true&useSSL=false
as suggested in https://stackoverflow.com/a/34449182
Question: Is there some change that we can make to the execution environment of our new target (Ubuntu kernel version 5.4.0-91) such that we need not make changes to the existing Java code?
You could resolve the SSL errors using the other method recommended in the error message itself:
You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
(emphasis mine)
That is, create a truststore file, and set the path to the truststore file and the password using Java properties. Then you can set those properties without changing code.
See https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-using-ssl.html for more details on creating the truststore file and use properties to specify it to your Java app.

How to implement Secure Connection to SAP HANA Database through JDBC?

I am able to connect to HANA database through my server using JDBC. I am trying to figure out if my connection to the database is secured or not?
public void createDataSource() {
DataSource dataSource = DataSourceBuilder.create()
.driverClassName(JDBC_DRIVER)
.URL("jdbc:sap://localhost:30013/?encrypt=true&validateCertificate=true")
.username("username")
.password("password")
.build();
Connection c = dataSource.getConnection();
}
Now what I have understood from Documentation is that when I set parameters in the URL such
encrypt=true
It enables or disables TLS encryption.
For SAP HANA Cloud connections to succeed, encrypt must be set to TRUE.
Another parameter is:
validateCertificate=true
If set to true, specifies that the server’s certificate is validated.
Here I am not able to understand is when it will be deployed to production is this all we need to do for a secure database connection? Can someone please point out what I am missing?
The neat thing about certificates is that you normally don't have to worry about them.
The java virtual machine (JVM) ships with a set of so-called root certificates of the certificate authorities (CAs) (including the DigiCert Global Root CA). Depending on the java distribution there might be slight differences, but the top CAs should be trusted by all.
You can see the list of trusted CAs with the following command:
keytool -list -v -cacerts -storepass changeit
As long as the SSL certificate of your database is signed by one of these CAs (or an intermediate of these) you don't need any additional configuration and it just works out of the box. This is as well the scenario, which you should aim for as it is the easiest.
You can however as well pass to the JVM your own list of trusted certificates or, and this is probably for a generic set up the best, set a custom truststore for a single JDBC connection. In a dockerized setup, you can e.g. make the whole database string configurable allowing people to mount their own CAs into the container and configure them in the database connection string. For details check the SAP documentation on the "JDBC Connection Properties for SAP HANA".
jdbc:sap://localhost:30013/?encrypt=true&validateCertificate=true&trustStore=file:cert/truststore.jks&trustStorePassword=123456

Is it safe to send a password via a Socket created by the default SSLSocketFactory?

Without second thought, I sent a password over a connection made using a Socket created by the default SSLSocketFactory in Java. For SSLSocketFactory.getDefault(), the documentation states the following:
Returns the default SSL socket factory.
The first time this method is called, the security property ssl.SocketFactory.provider is examined. If it is non-null, a class by that name is loaded and instantiated. If that is successful and the object is an instance of SSLSocketFactory, it is made the default SSL socket factory.
Otherwise, this method returns SSLContext.getDefault().getSocketFactory(). If that call fails, an inoperative factory is returned.
I'm sure that Java isn't trying to trick me, but since this was my first time doing this I wanted to make sure that it was something I can rely on for password encryption.
EDIT: At the end of the property data for "Set-Cookie" in the server response it says "HttpOnly" and then "Secure". I'm assuming that this means that the server has acknowledged that the connection was secure, but I am not 100% sure.
When you create a socket via the default SSLSocketFactory, and don't configure anything else with the relevant system and security properties, a set of certificate authorities bundled with your Java runtime is used. Glossing over some details, this is a trust store found in your JRE installation at lib/security/cacerts. The certificates included there those of widely accepted certificate authorities, similar to the list your browser might use.
So, if the server you connect to uses a certificate issued though one of these well-known authorities, the certificate will be validated, or the connection will fail.
This ensures that a trusted authority certified the identity of the party to whom you are connected. As long as their private key hasn't been stolen, you can rely on the name bound in the certificate.
But, you need to verify the host name to ensure that the certificate presented by the server matches the host name that you expect. This happens automatically when you use the URL class, but you need to set the HostNameVerifier explicitly when using SSLSocket directly.
Finally, even after you know who you are talking to, you still have to exercise judgment in the information you disclose to them. That's not a technical problem though.

Issue with DNS Naming and Certificates LDAP Context

With the last update of Java 1.8.0_181 I have an issue when I try to create a LDAPS connection to Active Directory. Up to version 1.8.0_171 using the following code I could create it without issues
Hashtable<String, Object> objEnvironment;
objEnvironment = new Hashtable<String, Object>(11);
objEnvironment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
objEnvironment.put(Context.PROVIDER_URL, "LDAPS://domain:636");
objEnvironment.put(Context.SECURITY_AUTHENTICATION, "simple");
objEnvironment.put(Context.SECURITY_PRINCIPAL, <username>);
objEnvironment.put(Context.SECURITY_CREDENTIALS, <Password>);
objEnvironment.put("java.naming.ldap.attributes.binary", <attributes>);
System.setProperty("javax.net.ssl.trustStore", "certificates".concat(File.separator).concat("cacerts"));
this.objLDAPContext = new InitialLdapContext(objEnvironment, null);
However with the last version I get the following exception java.security.cert.CertificateException: No DNS name found for xxxx.xxxx.xxx
Looking it up I found it is an issue with the FDQN name, if I do not use the same name as it is displayed on the certificate, I can not establish a connection. I would like to know if there is a way to skip this and can use the domain name as I did in the past.
The issue you are experiencing may be due to the changes introduced in Java 1.8.0_181 for improved LDAP Support. Refer the release notes here.
➜ Improve LDAP support Endpoint identification has been enabled on
LDAPS connections.
To improve the robustness of LDAPS (secure LDAP over TLS )
connections, endpoint identification algorithms have been enabled by
default.
Note that there may be situations where some applications that were
previously able to successfully connect to an LDAPS server may no
longer be able to do so. Such applications may, if they deem
appropriate, disable endpoint identification using a new system
property: com.sun.jndi.ldap.object.disableEndpointIdentification.
Define this system property (or set it to true) to disable endpoint
identification algorithms.
You may use the workaround to add -Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true while running your code.
Note that this is not a recommended approach, as your system will continue to be vulnerable using this approach.

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

Categories