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 #74

Merged
merged 12 commits into from
Sep 13, 2017
26 changes: 24 additions & 2 deletions src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -1622,7 +1622,22 @@ void enableSSL(String host,

tm = new TrustManager[] {new PermissiveX509TrustManager(this)};
}

// Otherwise, we'll check if a specific TrustManager implemenation has been requested and
// if so instantiate it, optionally specifying a constructor argument to customize it.
else if (con.getTrustManagerClass() != null) {
Class<?> tmClass = Class.forName(con.getTrustManagerClass());
if (!TrustManager.class.isAssignableFrom(tmClass)) {
throw new IllegalArgumentException(
"The class specified by the trustManagerClass property must implement javax.net.ssl.TrustManager");
}
String constructorArg = con.getTrustManagerConstructorArg();
if (constructorArg == null) {
tm = new TrustManager[] {(TrustManager) tmClass.getDeclaredConstructor().newInstance()};
}
else {
tm = new TrustManager[] {(TrustManager) tmClass.getDeclaredConstructor(String.class).newInstance(constructorArg)};
}
}
// Otherwise, we'll validate the certificate using a real TrustManager obtained
// from the a security provider that is capable of validating X.509 certificates.
else {
Expand Down Expand Up @@ -1798,7 +1813,14 @@ void enableSSL(String host,

// It is important to get the localized message here, otherwise error messages won't match for different locales.
String errMsg = e.getLocalizedMessage();

// If the message is null replace it with the non-localized message or a dummy string. This can happen if a custom
// TrustManager implementation is specified that does not provide localized messages.
if (errMsg == null) {
errMsg = e.getMessage();
}
if (errMsg == null) {
errMsg = "";
}
// The error message may have a connection id appended to it. Extract the message only for comparison.
// This client connection id is appended in method checkAndAppendClientConnId().
if (errMsg.contains(SQLServerException.LOG_CLIENT_CONNECTION_ID_PREFIX)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,20 @@ final byte getNegotiatedEncryptionLevel() {
return negotiatedEncryptionLevel;
}

private String trustManagerClass = null;

final String getTrustManagerClass() {
assert TDS.ENCRYPT_INVALID != requestedEncryptionLevel;
return trustManagerClass;
}

private String trustManagerConstructorArg = null;

final String getTrustManagerConstructorArg() {
assert TDS.ENCRYPT_INVALID != requestedEncryptionLevel;
return trustManagerConstructorArg;
}

static final String RESERVED_PROVIDER_NAME_PREFIX = "MSSQL_";
String columnEncryptionSetting = null;

Expand Down Expand Up @@ -1355,6 +1369,9 @@ Connection connectInternal(Properties propsIn,

trustServerCertificate = booleanPropertyOn(sPropKey, sPropValue);

trustManagerClass = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.TRUST_MANAGER_CLASS.toString());
trustManagerConstructorArg = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.TRUST_MANAGER_CONSTRUCTOR_ARG.toString());

sPropKey = SQLServerDriverStringProperty.SELECT_METHOD.toString();
sPropValue = activeConnectionProperties.getProperty(sPropKey);
if (sPropValue == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,24 @@ public String getSSLProtocol() {
SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue());
}

public void setTrustManagerClass(String trustManagerClass) {
setStringProperty(connectionProps, SQLServerDriverStringProperty.TRUST_MANAGER_CLASS.toString(), trustManagerClass);
}

public String getTrustManagerClass() {
return getStringProperty(connectionProps, SQLServerDriverStringProperty.TRUST_MANAGER_CLASS.toString(),
SQLServerDriverStringProperty.TRUST_MANAGER_CLASS.getDefaultValue());
}

public void setTrustManagerConstructorArg(String trustManagerClass) {
setStringProperty(connectionProps, SQLServerDriverStringProperty.TRUST_MANAGER_CONSTRUCTOR_ARG.toString(), trustManagerClass);
}

public String getTrustManagerConstructorArg() {
return getStringProperty(connectionProps, SQLServerDriverStringProperty.TRUST_MANAGER_CONSTRUCTOR_ARG.toString(),
SQLServerDriverStringProperty.TRUST_MANAGER_CONSTRUCTOR_ARG.getDefaultValue());
}

// The URL property is exposed for backwards compatibility reasons. Also, several
// Java Application servers expect a setURL function on the DataSource and set it
// by default (JBoss and WebLogic).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ enum SQLServerDriverStringProperty
TRUST_STORE_TYPE ("trustStoreType", "JKS"),
TRUST_STORE ("trustStore", ""),
TRUST_STORE_PASSWORD ("trustStorePassword", ""),
TRUST_MANAGER_CLASS ("trustManagerClass", ""),
TRUST_MANAGER_CONSTRUCTOR_ARG("trustManagerConstructorArg", ""),
USER ("user", ""),
WORKSTATION_ID ("workstationID", Util.WSIDNotAvailable),
AUTHENTICATION_SCHEME ("authenticationScheme", AuthenticationScheme.nativeAuthentication.toString()),
Expand Down Expand Up @@ -411,6 +413,8 @@ public final class SQLServerDriver implements java.sql.Driver {
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.TRUST_STORE_TYPE.toString(), SQLServerDriverStringProperty.TRUST_STORE_TYPE.getDefaultValue(), false, null),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.TRUST_STORE.toString(), SQLServerDriverStringProperty.TRUST_STORE.getDefaultValue(), false, null),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.TRUST_STORE_PASSWORD.toString(), SQLServerDriverStringProperty.TRUST_STORE_PASSWORD.getDefaultValue(), false, null),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.TRUST_MANAGER_CLASS.toString(), SQLServerDriverStringProperty.TRUST_MANAGER_CLASS.getDefaultValue(), false, null),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.TRUST_MANAGER_CONSTRUCTOR_ARG.toString(), SQLServerDriverStringProperty.TRUST_MANAGER_CONSTRUCTOR_ARG.getDefaultValue(), false, null),
new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.toString(), Boolean.toString(SQLServerDriverBooleanProperty.SEND_TIME_AS_DATETIME.getDefaultValue()), false, TRUE_FALSE),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.USER.toString(), SQLServerDriverStringProperty.USER.getDefaultValue(), true, null),
new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.WORKSTATION_ID.toString(), SQLServerDriverStringProperty.WORKSTATION_ID.getDefaultValue(), false, null),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,8 @@ protected Object[][] getContents() {
{"R_trustStoreTypePropertyDescription", "KeyStore type."},
{"R_trustStorePropertyDescription", "The path to the certificate TrustStore file."},
{"R_trustStorePasswordPropertyDescription", "The password used to check the integrity of the trust store data."},
{"R_trustManagerClassPropertyDescription", "The class to instantiate as the TrustManager for SSL connections."},
{"R_trustManagerConstructorArgPropertyDescription", "The optional argument to pass to the constructor specified by trustManagerClass."},
{"R_hostNameInCertificatePropertyDescription", "The host name to be used when validating the SQL Server Secure Sockets Layer (SSL) certificate."},
{"R_sendTimeAsDatetimePropertyDescription", "Determines whether to use the SQL Server datetime data type to send java.sql.Time values to the database."},
{"R_TransparentNetworkIPResolutionPropertyDescription", "Determines whether to use the Transparent Network IP Resolution feature."},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.microsoft.sqlserver.jdbc.ssl.trustmanager;

import java.sql.DriverManager;

import static org.junit.Assert.*;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;

import com.microsoft.sqlserver.jdbc.SQLServerConnection;
import com.microsoft.sqlserver.jdbc.SQLServerException;
import com.microsoft.sqlserver.testframework.AbstractTest;

@RunWith(JUnitPlatform.class)
public class CustomTrustManagerTest extends AbstractTest {

/**
* Connect with a permissive Trust Manager that always accepts the X509Certificate chain offered to it.
*
* @throws Exception
*/
@Test
public void testWithPermissiveX509TrustManager() throws Exception {
String url = connectionString + ";trustManagerClass=" + PermissiveTrustManager.class.getName() + ";encrypt=true;";
try (SQLServerConnection con = (SQLServerConnection) DriverManager.getConnection(url)) {
assertTrue(con != null);
}
}

/**
* Connect with a Trust Manager that requires trustManagerConstructorArg.
*
* @throws Exception
*/
@Test
public void testWithTrustManagerConstructorArg() throws Exception {
String url = connectionString + ";trustManagerClass=" + TrustManagerWithConstructorArg.class.getName()
+ ";trustManagerConstructorArg=dummyString;" + ";encrypt=true;";
try (SQLServerConnection con = (SQLServerConnection) DriverManager.getConnection(url)) {
assertTrue(con != null);
}
}

/**
* Test with a custom Trust Manager that does not implement X509TrustManager.
*
* @throws Exception
*/
@Test
public void testWithInvalidTrustManager() throws Exception {
String url = connectionString + ";trustManagerClass=" + InvalidTrustManager.class.getName() + ";encrypt=true;";
try (SQLServerConnection con = (SQLServerConnection) DriverManager.getConnection(url)) {
fail();
}
catch (SQLServerException e) {
assertTrue(e.getMessage().contains("The class specified by the trustManagerClass property must implement javax.net.ssl.TrustManager"));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.microsoft.sqlserver.jdbc.ssl.trustmanager;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/**
* This class does not implement X509TrustManager and the connection must fail when it is specified by the trustManagerClass property
*
*/
public final class InvalidTrustManager {
public InvalidTrustManager() {
}

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

}

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

public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.microsoft.sqlserver.jdbc.ssl.trustmanager;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

/**
* This class implements an X509TrustManager that always accepts the X509Certificate chain offered to it.
*/

public final class PermissiveTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}

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

public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.microsoft.sqlserver.jdbc.ssl.trustmanager;

import java.io.IOException;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.GeneralSecurityException;
import javax.net.ssl.X509TrustManager;

/**
* This class implements an X509TrustManager that always accepts the X509Certificate chain offered to it.
*
* The constructor argument certToTrust is a dummy string used to test trustManagerConstructorArg.
*
*/

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

public TrustManagerWithConstructorArg(String certToTrust) throws IOException, GeneralSecurityException {
trustManager = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}

@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}

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

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

@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}