Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add connection properties for specifying custom TrustManager #70

Closed
wants to merge 1 commit into from

Conversation

sehrope
Copy link
Contributor

@sehrope sehrope commented Dec 6, 2016

This PR adds two new connection properties:

  • trustManagerClass - The fully qualified class name of a custom javax.net.ssl.TrustManager
  • trustManagerConstructorArg - An optional argument to pass to the constructor of the TrustManager.

If trustManagerClass is specified and an encrypted connection is requested, the custom TrustManager will be used rather than the default system JVM keystore based TrustManager.

The primary use case for this is to dynamically specify the specific set of certificates that are trusted on a per connection basis without modifying the global settings for the JVM environment (i.e. no need to use a shared / global JKS).

For the error message localization code on line 1803, I've added handling for the case where the caught exception does not provide a localized message. That situation can arise when a custom TrustManager implementation is used as the Driver has no control over that code (it's use specified).

The new code tries to get a localized message, if one is not available then it tries the non-localized message, and if that is not available it defaults to an empty string. The final empty string default is required as subsequent code assumes that all caught exceptions will have a localized error message and would otherwise throw a NullPointerException from within the error handling code.

There's no additional tests for it in the test suite but I've tested it externally using both the specific custom TrustManager we'll be using as well as custom implementation of PermissiveX509TrustManager (which effectively mimics the behavior of trustServerCertificate=true).

Adds two new connection properties that can be used to specify a custom
TrustManager implementation:

trustManagerClass - Class name of the custom TrustManager

trustManagerConstructorArg - Optional argument to pass to the
constructor of the custom TrustManager.

If encryption is enabled and the trustManagerClass property is specified,
it will be retrieved via Class.forName(...).

If the optional property trustManagerConstructorArg is specified, then a
constructor will be retrieved via getDeclaredConstructors(String.class).
The TrustManager will then be instantiated by specified the optional
argument as a parameter.

If the optional property trustManagerConstructorArg is not specfied,
then the default no argument constructor of the class will be retrieved
and instantiated.
@xiangyushawn xiangyushawn added the Under Review Used for pull requests under review label Dec 7, 2016
@xiangyushawn xiangyushawn self-assigned this Dec 7, 2016
@v-nisidh
Copy link
Contributor

v-nisidh commented Dec 7, 2016

@sehrope I like your implementation. As per my understanding you are adding ability to configure 3rd party / Custom TrustManager's. Do you have any specific scenario where you need this kind of requiement?

@sehrope
Copy link
Contributor Author

sehrope commented Dec 7, 2016

Yes the specific scenario is a dynamic environment where connections to disparate databases are created on the fly (check out our product JackDB 😄).

Using the standard keystore based approach isn't feasible as you'd be constantly adding / removing to the same shared keystore file. That also has the disadvantage of requiring a file on the local filesystem which preferably avoided in PaaS style deployments.

With this patch, we can customize each connection with a custom TrustManager that validates the specific remote server certificate that we're expecting.

Below is an example of a TrustManager that takes a single certificate as an argument and validates againt it. Note that the remote certificate does not have to byte-for-byte match the constructor argument. The chain is what's being validated so you can specify a parent certificate as the constructor argument and all child certificates signed by that parent will be trusted as well. This works particularly well in DBaaS environments (ex: like SQL Azure) where each server would have a unique certificate but they'd all be signed by a common parent.

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.util.UUID;

import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.GeneralSecurityException;

import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

public class SingleCertTrustManager implements X509TrustManager {
    X509Certificate cert;
    X509TrustManager trustManager;        

    public SingleCertTrustManager(String certToTrust) throws IOException, GeneralSecurityException {
    	InputStream in = new ByteArrayInputStream(certToTrust.getBytes());
        KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
        try {
            // Note: KeyStore requires it be loaded even if you don't load anything into it:
            ks.load(null);
        } catch (Exception e) {
        }
        CertificateFactory cf = CertificateFactory.getInstance("X509");
        cert = (X509Certificate) cf.generateCertificate(in);
        ks.setCertificateEntry(UUID.randomUUID().toString(), cert);
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        for (TrustManager tm : tmf.getTrustManagers()) {
            if (tm instanceof X509TrustManager) {
                trustManager = (X509TrustManager) tm;
                break;
            }
        }
        if (trustManager == null) {
            throw new GeneralSecurityException("No X509TrustManager found");
        }
    }

    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        trustManager.checkServerTrusted(chain, authType);
    }

    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[] { cert };
    }
}

@xiangyushawn xiangyushawn assigned v-nisidh and unassigned xiangyushawn Dec 7, 2016
@v-nisidh
Copy link
Contributor

v-nisidh commented Dec 7, 2016

@sehrope I got crux of requirement. You want a way to validate direct certificate without passing key-store. I appreciate your solution & approach. Right now your suggestion is in review process. Mean while can you open this Pull Request for dev branch?

@sehrope
Copy link
Contributor Author

sehrope commented Dec 7, 2016

Done. I've created #74 targeting the dev branch.

@josecollazzi
Copy link

is there any plans to merge this soon?

@ajlam
Copy link
Member

ajlam commented Sep 11, 2017

@josecollazzi, #74 was created against the dev branch. Please follow updates there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Under Review Used for pull requests under review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants