using custom keystore and jsseimplementation when upgrading to tomcat 8.5 - java

We were using our own custom keystore and also provided the custom class implementation using JSSEImplementation and ServerSocketFactory and configured both in server.xml for "store" and "sslImplementation" attributes.
But now upgrading to 8.5, I started getting lot of ClassNotFoundException for JSSESocketFactory etc.
Doing little more research I found that they have removed many classes and methods like JSSESocketFactory.java, getServerSocketFactory(), getSSLUtil(AbstractEndpoint endpoint) etc.
So now, my question is:
is there any way in apache tomcat 8.5 in which I can configure my custom keystore under "store" in server.xml and use my own sslImplementation?
I was using AbstractEndpoint in the method signature to get the store name set in server.xml and then loading that keystore in MyJSSESocketFactory like this:
public class MySSLImplementation extends JSSEImplementation
{
#Override
public org.apache.tomcat.util.net.ServerSocketFactory getServerSocketFactory(
AbstractEndpoint endpoint) {
kStore = endpoint.getProperty("store");
return new MyJSSESocketFactory(endpoint, kStore);
}
}
public class MyJSSESocketFactory extends JSSESocketFactory {
private final AbstractEndpoint _endpoint;
private final String store;
public MyJSSESocketFactory(AbstractEndpoint endpoint, String store) {
super(endpoint);
this._endpoint = endpoint;
this.store = store;
}
/*
* Gets the SSL server's keystore.
*/
#Override
protected KeyStore getKeystore(String type, String provider, String pass)
throws IOException {
if ("MYKS".equalsIgnoreCase(type)) {
String keystoreName = store;
KeyStore ks = null;
try {
if (provider == null || provider.isEmpty()) {
ks = KeyStore.getInstance(type);
} else {
ks = KeyStore.getInstance(type, provider);
}
MyStoreParameter params = new MyStoreParameter(
keystoreName);
ks.load(params);
} catch (Exception ex) {
throw new IOException(
"Failed to load keystore " + keystoreName, ex);
}
return ks;
} else {
return super.getKeystore(type, provider, pass);
}
}
}
"MYKS" is set in server.xml for "store" attribute

For whatever it's worth, this is the commit that broke it:
Remove BIo specific JSSE code
Here is some of the rationale for removing it:
https://github.com/spring-projects/spring-boot/issues/6164
#markt-asf There appears to be a number of breaking changes in 8.5.3
(upgrading from 8.0.33):
Maven artifact org.apache.tomcat.embed:tomcat-embed-logging-juli no longer exists
Class org.apache.tomcat.util.net.ServerSocketFactory no longer exists
Class org.apache.tomcat.util.net.jsse.JSSESocketFactory no longer exists
Method JSSEImplementaton.getServerSockerFactory(AbstractEndpoint) no longer exists
Method JSSEImplementaton.getSSLUtil(AbstractEndpoint) no longer exists
Http11NioProtocol.getEndpoint() is no longer visible
Field org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML no longer exists
AbstractHttp11Protocol.setCompressableMimeTypes no longer exists
Reply:
1 is only required to enable container logging via log4j 1.x and that
version is no longer supported by the log4j community. log4j 2.x can
be used for container logging without any extra libraries.
2-6 are side effects of the connector refactoring in 8.5.x / 9.0.x.
They are all low-level internal APIs that I'm a little surprised to
find boot is using.
7 was part of the mechanism used to pass web.xml to Jasper for
processing. It was removed as it was no longer required as of Servlet
3.0 as all the necessary information was available via the standard Servlet API.
8 That was some API clean-up. That one could be restored fairly easily
for 8.5.x.
The commit was made in Nov, 2014.
As of Tomcat 8.0, the class was still there - and NOT on the *deprecated" list:
https://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/tomcat/util/net/jsse/JSSESocketFactory.html
Here is the changelog that discussed "removing BIO" ("Blocking I/O"):
Migrating from Tomcat 8.0 to 8.5
Finallly, comparing these two links might help:
SSL/TLS Configuration HOW-TO Tomcat 8.0.39
SSL/TLS Configuration HOW-TO Tomcat 8.5.9

Related

Facing authentication cache issue with generated Client STUB for server

I found a peculiar issue while using client stub which was generated in past using JAX-WS(wsimport) for SSRS server. Once the test connection is successful with configured username and password, then all the subsequent connection passes, even for the incorrect configuration.
Application uses default Authenticator to set credentials.
Authenticator.setDefault(
new Authenticator() {
#Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(
getUserName(),getPassword().toCharArray());
}
});
Later, I dig deeper and found out that sun package HttpURLConnection uses the cached authorization key for connection from the AuthCache implementation. Worth to mention that it is part of sun package.
package sun.net.www.protocol.http;
public class AuthCacheImpl implements AuthCache {
...
...
}
Now, current project is running with JDK11 which restricts to use the protected classes from sun package.
Now, the client stub is fairly straightforward to understand.
ReportingService2010 reportingService2010 = new ReportingService2010(reportServerUrl);
ReportingService2010Soap reportingService2010Soap = reportingService2010.getReportingService2010Soap();
reportingService2010Soap.listChildern(itemPath,isRecursive);
Request from above mentioned code calls the HttpURLConnection implementation of sun package and tries to read the auth key from cache whether it exists or not. If yes, than use it without considering new credentials and connects, else add the auth key to the AuthCache for future usage.
package sun.net.www.protocol.http;
public class HttpURLConnection extends java.net.HttpURLConnection {
...
...
private AuthenticationInfo getServerAuthentication (AuthenticationHeader authhdr) {
...
...
serverAuthKey = AuthenticationInfo.getServerAuthKey(url, realm, authScheme,
getAuthenticatorKey());
ret = AuthenticationInfo.getServerAuth(serverAuthKey);
...
...
}
...
...
}
Is there any way to handle this issue ? Or any other alternative ?
PS - restart of server clears the cache but thats an ugly option.

Tomcat 8 - LDAP: NameNotFoundException error code 32, remaining name empty string

Trying to migrate an application from WebLogic 12.2.1 to Tomcat 8.5.4, what under Weblogic was an entry as Foreign JNDI Providers for an LDAP connection has been migrated to a new Resource under Tomcat.
Following this advice on Stack Overflow, a custom LdapContextFactory has been packaged as a new jar file under Tomcat lib folder.
In the Tomcat server.xml file the following GlobalNamingResources/Resource has been configured:
<Resource name="ldapConnection"
auth="Container"
type="javax.naming.ldap.LdapContext"
factory="com.sample.custom.LdapContextFactory"
singleton="false"
java.naming.referral="follow"
java.naming.factory.initial="com.sun.jndi.ldap.LdapCtxFactory"
java.naming.provider.url="ldap://some.host:389"
java.naming.security.authentication="simple"
java.naming.security.principal="CN=some,OU=some,OU=some,DC=some,DC=a,DC=b"
java.naming.security.credentials="password"
com.sun.jndi.ldap.connect.pool="true"
com.sun.jndi.ldap.connect.pool.maxsize="10"
com.sun.jndi.ldap.connect.pool.prefsize="4"
com.sun.jndi.ldap.connect.pool.timeout="30000" />
The connection above works fine when browsing the LDAP directory via an LDAP browser like Apache Directory Studio / LDAP Browser embedded in Eclipse.
The custom com.sample.custom.LdapContextFactory is quite simple:
public class LdapContextFactory implements ObjectFactory {
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
throws Exception {
Hashtable<Object, Object> env = new Hashtable<>();
Reference reference = (Reference) obj;
Enumeration<RefAddr> references = reference.getAll();
while (references.hasMoreElements()) {
RefAddr address = references.nextElement();
String type = address.getType();
String content = (String) address.getContent();
env.put(type, content);
}
return new InitialLdapContext(env, null);
}
}
However, at start-up Tomcat is throwing the following exception:
07-Sep-2016 15:04:01.064 SEVERE [main] org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans Exception processing Global JNDI Resources
javax.naming.NameNotFoundException: [LDAP: error code 32 - 0000208D: NameErr: DSID-031001E5, problem 2001 (NO_OBJECT), data 0, best match of:
''
]; remaining name ''
at com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3160)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:3081)
at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2888)
at com.sun.jndi.ldap.LdapCtx.c_listBindings(LdapCtx.java:1189)
at com.sun.jndi.toolkit.ctx.ComponentContext.p_listBindings(ComponentContext.java:592)
at com.sun.jndi.toolkit.ctx.PartialCompositeContext.listBindings(PartialCompositeContext.java:330)
at com.sun.jndi.toolkit.ctx.PartialCompositeContext.listBindings(PartialCompositeContext.java:317)
at javax.naming.InitialContext.listBindings(InitialContext.java:472)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:136)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:145)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.createMBeans(GlobalResourcesLifecycleListener.java:110)
at org.apache.catalina.mbeans.GlobalResourcesLifecycleListener.lifecycleEvent(GlobalResourcesLifecycleListener.java:82)
at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:94)
at org.apache.catalina.util.LifecycleBase.setStateInternal(LifecycleBase.java:401)
at org.apache.catalina.util.LifecycleBase.setState(LifecycleBase.java:345)
at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:784)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:152)
at org.apache.catalina.startup.Catalina.start(Catalina.java:655)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:355)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:495)
Similar questions and investigations suggest an invalid LDAP DN, but:
The same LDAP configuration works fine via an LDAP Client
No search is actually performed, at start-up time Tomcat throws this exception without any query
The error suggests an empty string '' as remaining name, hence not really something not found, apparently
Question(s): Is this the correct way to migrate an Foreign JNDI Providers entry from WebLogic to Tomcat? How to fix an invalid LDAP DN entry with an empty remaining name? Could it be a missing baseDN to configure somewhere?
Update
The same exact error happens when changing the LdapContextFactory to the following, as suggested via comments:
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
throws Exception {
Hashtable<Object, Object> env = new Hashtable<>();
Reference reference = (Reference) obj;
Enumeration<RefAddr> references = reference.getAll();
String providerUrl = "no valid URL";
while (references.hasMoreElements()) {
RefAddr address = references.nextElement();
String type = address.getType();
String content = (String) address.getContent();
switch (type) {
case Context.PROVIDER_URL:
env.put(Context.PROVIDER_URL, content);
providerUrl = content;
break;
default:
env.put(type, content);
break;
}
}
InitialLdapContext context = null;
Object result = null;
try {
context = new InitialLdapContext(env, null);
LOGGER.info("looking up for " + providerUrl);
result = context.lookup(providerUrl);
} finally {
if (context != null) {
context.close();
}
}
LOGGER.info("Created new LDAP Context");
return result;
}
Change is confirmed via logging, to make sure it was deployed properly.
The involved listener is defined by default at the top of the server.xml file as
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
And cannot be disabled as per official documentation:
The Global Resources Lifecycle Listener initializes the Global JNDI resources defined in server.xml as part of the Global Resources element. Without this listener, none of the Global Resources will be available.
The same also happens on Tomcat version 8.5.5 and 7.0.69: simply adding the new global resource as above and the additional jar providing the factory above, the exception pointing at an empty remaining name will be thrown.
The stacktrace went away by appending to the java.naming.provider.url property the LDAP schema DN, using the first factory implementation provided in the question.
Below a screenshot of the LDAP client used in this context, the Apache Directory Studio / LDAP Browser embedded in Eclipse, from which it was possible to browse the concerned LDAP simply using the initial values of the question.
By appending the schema DN of the Root element to the connection URL, the exception went away and the LDAP resource is now shared via JNDI in Tomcat 8.
Further details as outcome of the troubleshooting:
In Tomcat 8 global resources are handled via a global resource listener, the GlobalResourcesLifecycleListener, defined by default in the server.xml file. Such a listener invokes a context.listBindings("") on bean creation, hence effectively browsing the LDAP directory.
This initial browsing may most probably be the difference between Tomcat and WebLogic, where LDAP is looked up via JNDI only when required, hence via direct query, rather than at start-up with a generic query. As such, in Tomcat the LDAP url would need further details, that is, a slightly different configuration as part of its url to directly point to a valid base DN.
From official WebLogic documentation:
On start up, WebLogic Server attempts to connect to the JNDI source. If the connection is successful, WebLogic Server sets up the requested objects and links in the local JNDI tree, making them available to WebLogic Server clients.
Hence, a connection is rather simpler than a listBindings:
Enumerates the names bound in the named context, along with the objects bound to them. The contents of any subcontexts are not included.

Play 2.4 HikariCP connection pool configuration

I need to specify some config parameters, separated by dots. Connection pool is in Play 2.4 application. For example
db {
default {
driver = ${?DB_DRIVER}
url = ${?DB_URL}
username = ${?DB_USER}
password = ${?DB_PASSWORD}
hikaricp {
dataSource {
"javax.net.ssl.trustStore" = ${?DB_TRUST_STORE}
"javax.net.ssl.trustStoreType" = "JKS"
"javax.net.ssl.trustStorePassword" = ${?DB_TRUST_STORE_PASSWORD}
"javax.net.ssl.keyStore" = ${?DB_KEY_STORE}
"javax.net.ssl.keyStoreType" = "JKS"
"javax.net.ssl.keyStorePassword" = ${?DB_KEY_STORE_PASSWORD}
}
}
}
All parameters like "javax.net.ssl." are used to provide details about SSL certificates for connection. Looks like Play framework is trying to parse config keys like "javax.net.ssl." and separate them by dots. So it fails with the exception
Caused by: com.typesafe.config.ConfigException$Missing: No configuration setting found for key 'javax'
I found a similar topic here: How do I get an unwrapped key in Typesafe Config?
According to the first response
foo {
bar {
baz = 10
}
}
is the same as
foo.bar.baz = 10
But it would be different if written as "foo.bar.baz" = 10
I hoped that using quotes should help but it doesn't and seems like a bug in the pool configuration implementation. Please, advise.
I answer to complete #brettw answer.
You have to add these parameters as JVM properties when running your play exec :
/path/to/bin/<project-name>
-Djavax.net.ssl.keyStore=/mysql-credentials/keystore \
-Djavax.net.ssl.keyStorePassword=YYYYYY \
-Djavax.net.ssl.trustStore=/mysql-credentials/truststore \
-Djavax.net.ssl.trustStorePassword=XXXXXX \
...
For those wondering how to create these stores : read this
I think that trustStoreType and keyStoreType are not required, I always use JKS types.
Keep in mind that you also have to tell jdbc to use SSL :
-Dslick.dbs.default.db.url=jdbc:mysql://DOMAIN_OR_IP/DATABASE?verifyServerCertificate=false&useSSL=true&requireSSL=true
Last but not least you can debug the handshakes with :
-Djavax.net.debug=all
It gives you a lot (MB) of informations on handshakes, renegociation and ciphers printed on stdout
These are typically JVM properties, do I don't know if it is appropriate to put them in the db/dataSource configuration. Even if it is the driver that uses these, those are system-wide properties and would apply to all SSL components.

How to add multiple truststore paths to “java.net.ssl.trustStore”?

I want my Java Code to search for CA certificate of the server in one keystore... if it is unable to find the specific certificate (which I think will be known only when I try to connect via LDAP to Directory Server), it should look for the certificate in another keystore, whose path I know.
I tried this:
System.setProperty("javax.net.ssl.trustStore", System.getProperty("java.home") + "/lib/security/cacerts" + System.getProperty("path.separator") + path/to/second/keystore);
But it didn't seem to work.
Adding only one path (either of them) works, i.e. it runs like charm if certificate is found and fails if not.
So my question is:
Is there a method to add multpile keystore paths to javax.net.ssl.trustStore?
If it is not possible how should I write my code (I am asking for the algorithm) so that it just not throw Exception after first search itself and fail?
P.S. : I am not much familiar with Java.
Below is the relevant section of my code:
if(useSSL)
{
try
{
SSLContext se = SSLContext.getInstance("TLS");
Security.addProvider(se.getProvider());
}
catch(NoSuchAlgorithmException e) { }
System.setProperty("javax.net.ssl.trustStore", System.getProperty("java.home") + "/lib/security/cacerts");
com.org.ldap.LDAPSocketFactory ssf = new LDAPJSSESecureSocketFactory();
LDAPConnection.setSocketFactory(ssf);
}
try
{
lc = new LDAPConnection();
lc.connect( ldapServer, ldapPort);
lc.bind( ldapVersion, ldapUser, (userInfo[1]).getBytes() );
}
catch (LDAPException le)
{
le.printStackTrace();
}
You can't have multiple paths for javax.net.ssl.trustStore.
The easiest would be to make a local copy of the JRE's cacerts and import the certificates from your other store into it (effectively merging them). (See keytool -importkeystore.)
Otherwise, if you know in advance that all your LDAP connections will use your second keystore (and you also want to be able to use the default truststore for other, non-related connections), you could configure that trust store for that SSLSocketFactory only. I'm not familiar with com.org.ldap.LDAPSocketFactory, but it might have an option to do so. (Otherwise, you could create your custom SSLContext initialised with your second truststore and get an SSLSocketFactory, as described in this answer).
Another, more complicated way, would be to create a custom X509TrustManager that wraps the default trust manager, catches its exceptions and tries again with another trust manager initialised using your second store. It's feasible, but you'd need to make sure it still throws the exception if neither trust managers accept your certificate (otherwise, there would be a security hole). If you're not familiar with the JSSE API (or Java altogether), it's probably not the best option.
In addition, be careful when you use System.setProperty("javax.net.ssl.trustStore", ...) in your code: it is what's read to initialise the default SSLContext, but the default SSLContext is only initialised once, the first time it's required. Setting this system property afterwards would have no effect (unless of course, other classes from other libraries also rely on this value).
It's also not clear what you're trying to achieve with this, since you'll always ever succeed to add a security provider that's already there:
try
{
SSLContext se = SSLContext.getInstance("TLS");
Security.addProvider(se.getProvider());
}
catch(NoSuchAlgorithmException e) { }
No, just import all the certificates from one truststore into the other, and use the second.
Although it is a bit late, I would like to make a remark on the answer which the others have provided. It was nearly impossible indeed and with a lot of custom code it would be possible actually, we have seen it within an answer of a similar question before: Using a custom truststore in java as well as the default one
After encountering the same challenge for multiple projects I thought it would be handy to create a library and also make it publicly available to contribute back to the community. Please have a look here: Github - SSLContext-Kickstart
The usage for your use case would be for loading the jdk truststore and your own truststore:
import nl.altindag.sslcontext.SSLFactory;
import javax.net.ssl.SSLContext;
import java.security.cert.X509Certificate;
import java.util.List;
public class App {
public static void main(String[] args) {
String trustStorePath = ...;
char[] password = "password".toCharArray();
SSLFactory sslFactory = SSLFactory.builder()
.withDefaultTrustMaterial()
.withTrustMaterial(trustStorePath, password)
.build();
SSLContext sslContext = sslFactory.getSslContext();
List<X509Certificate> trustedCertificates = sslFactory.getTrustedCertificates();
}
}

Java HttpURLConnection uses SOCKS proxy instead of HTTP

I have a very simple code that uses HttpURLConnection to access some web site via proxy
System.setProperty("java.net.useSystemProxies", "true");
System.out.println("Proxy: " + ProxySelector.getDefault().select(new URI(urlS)));
URL url = new URL(urlS);
HttpURLConnection ic = (HttpURLConnection)url.openConnection();
ic.connect();
For some reason, Java thinks that I need SOCKS proxy, not http, throwing the following exception:
ERROR: Can't connect to SOCKS proxy:Connection timed out: connect
If you are having this issues on Windows, you may run into a Java bug.
Java treats any system proxy setting as SOCKS. You have to either disable useSystemProxies or don't use proxy in Windows.
If proxy is needed, try to uncheck "Use the same proxy server for all protocols", making sure the field for the SOCKS proxy is blank. That fixed our problem.
The real problem is that Java assumes that the "Use the same proxy server for all protocols" check affects SOCKS proxy too (I don't know the logic behind this dialog in Windows, but it is, at least, confusing)
If the check is set, you get proxies enabled for both HTTP and SOCKS, wich is very unlikely to be the desired configuration.
One way to solve it is unchecking the check and leaving blank the SOCKS field.
I finally solved it creating a ProxySelector wich first calls the default selector and if it finds the same configuration for HTTP and SOCKS connections, it omits the SOCKS proxy.
public class SocksFixerProxySelector extends ProxySelector {
ProxySelector base;
public SocksFixerProxySelector() {
base = ProxySelector.getDefault();
}
#Override
public List<Proxy> select(URI uri) {
List<Proxy> baseList = base.select(uri);
try {
if (uri.getScheme().equals("socket")) {
Proxy socksProxy = findByType(baseList, Type.SOCKS);
if (socksProxy != null) {
URI httpTestUri = new URI("http", uri.getHost(), uri.getPath(), uri.getFragment());
Proxy httpProxy = findByType(base.select(httpTestUri), Type.HTTP);
if (httpProxy != null && socksProxy.address().equals(httpProxy.address())) {
// Quitamos SOCKS
List<Proxy> filteredList = new ArrayList<>(baseList);
filteredList.remove(socksProxy);
return filteredList;
}
}
}
} catch (Exception e) {
}
return baseList;
}
#Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
base.connectFailed(uri, sa, ioe);
}
private Proxy findByType(List<Proxy> proxies, Proxy.Type type) {
for (Proxy proxy : proxies) {
if (proxy.type() == type)
return proxy;
}
return null;
}
Maybe a better solution would be to inspect the registry and detect the right settings, but I didn't want to mess with Windows specific code (and all those script settings looked bad, too )
You need to use the http.proxyHost system property instead. See http://java.sun.com/javase/6/docs/technotes/guides/net/proxies.html for details.
java -Dhttp.proxyHost=webcache.mydomain.com GetURL
Check that something has not set the "socksProxyHost" property in the Systems properties.
EDIT
The "useSystemProxies" property is described thus:
"On recent Windows systems and on Gnome 2.x platforms it is possible to tell the default ProxySelector to use the system proxy settings (both recent versions of Windows and Gnome 2.x let you set proxies globally through their user interface). If the system property java.net.useSystemProxies is set to true (by default it is set to false for compatibility sake), then the default ProxySelector will try to use these settings."
So, assuming that you have not supplied your own ProxySelector class, you should also check the system proxy settings to ensure that they don't say to use SOCKS.

Categories