From 971fd93853e4574493309daf8ead45a9bf319b9a Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Tue, 7 Mar 2017 11:36:37 -0800 Subject: [PATCH 1/2] constrained delegation --- .../sqlserver/jdbc/KerbAuthentication.java | 82 ++++++++++++------- .../sqlserver/jdbc/SQLServerConnection.java | 29 ++++++- .../sqlserver/jdbc/SQLServerDriver.java | 49 ++++++++--- .../sqlserver/jdbc/SQLServerResource.java | 1 + 4 files changed, 121 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java index 6706d84a6..d84058e58 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java @@ -127,37 +127,47 @@ private void intAuthInit() throws SQLServerException { // If we need to support NTLM as well, we can use null // Kerberos OID Oid kerberos = new Oid("1.2.840.113554.1.2.2"); - Subject currentSubject = null; - try { - AccessControlContext context = AccessController.getContext(); - currentSubject = Subject.getSubject(context); - if (null == currentSubject) { - lc = new LoginContext(CONFIGNAME); - lc.login(); - // per documentation LoginContext will instantiate a new subject. - currentSubject = lc.getSubject(); - } - } - catch (LoginException le) { - con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), le); - } - // http://blogs.sun.com/harcey/entry/of_java_kerberos_and_access - // We pass null to indicate that the system should interpret the SPN as it is. + // We pass null to indicate that the system should interpret the SPN + // as it is. GSSName remotePeerName = manager.createName(spn, null); - if (authLogger.isLoggable(Level.FINER)) { - authLogger.finer(toString() + " Getting client credentials"); - } - peerCredentials = getClientCredential(currentSubject, manager, kerberos); - if (authLogger.isLoggable(Level.FINER)) { - authLogger.finer(toString() + " creating security context"); + + if (null != peerCredentials) { + peerContext = manager.createContext(remotePeerName, kerberos, peerCredentials, GSSContext.DEFAULT_LIFETIME); + peerContext.requestCredDeleg(false); + peerContext.requestMutualAuth(true); + peerContext.requestInteg(true); } + else { + Subject currentSubject = null; + try { + AccessControlContext context = AccessController.getContext(); + currentSubject = Subject.getSubject(context); + if (null == currentSubject) { + lc = new LoginContext(CONFIGNAME); + lc.login(); + // per documentation LoginContext will instantiate a new subject. + currentSubject = lc.getSubject(); + } + } + catch (LoginException le) { + con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), le); + } - peerContext = manager.createContext(remotePeerName, kerberos, peerCredentials, GSSContext.DEFAULT_LIFETIME); - // The following flags should be inline with our native implementation. - peerContext.requestCredDeleg(true); - peerContext.requestMutualAuth(true); - peerContext.requestInteg(true); + if (authLogger.isLoggable(Level.FINER)) { + authLogger.finer(toString() + " Getting client credentials"); + } + peerCredentials = getClientCredential(currentSubject, manager, kerberos); + if (authLogger.isLoggable(Level.FINER)) { + authLogger.finer(toString() + " creating security context"); + } + + peerContext = manager.createContext(remotePeerName, kerberos, peerCredentials, GSSContext.DEFAULT_LIFETIME); + // The following flags should be inline with our native implementation. + peerContext.requestCredDeleg(true); + peerContext.requestMutualAuth(true); + peerContext.requestInteg(true); + } } catch (GSSException ge) { @@ -182,7 +192,7 @@ public GSSCredential run() throws GSSException { } }; // TO support java 5, 6 we have to do this - // The signature for Java 5 returns an object 6 returns GSSCredential, immediate casting throws + // The signature for Java 5 returns an object 6 returns GSSCredential, immediate casting throws // warning in Java 6. Object credential = Subject.doAs(subject, action); return (GSSCredential) credential; @@ -262,6 +272,22 @@ private String makeSpn(String server, } } + /** + * + * @param con + * @param address + * @param port + * @param ImpersonatedUserCred + * @throws SQLServerException + */ + KerbAuthentication(SQLServerConnection con, + String address, + int port, + GSSCredential ImpersonatedUserCred) throws SQLServerException { + this(con, address, port); + peerCredentials = ImpersonatedUserCred; + } + byte[] GenerateClientContext(byte[] pin, boolean[] done) throws SQLServerException { if (null == peerContext) { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index 87ee15d18..62ed954eb 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -52,6 +52,9 @@ import javax.sql.XAConnection; import javax.xml.bind.DatatypeConverter; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; + /** * SQLServerConnection implements a JDBC connection to SQL Server. SQLServerConnections support JDBC connection pooling and may be either physical * JDBC connections or logical JDBC connections. @@ -505,6 +508,7 @@ static synchronized List getColumnEncryptionTrustedMasterKeyPaths(String Properties activeConnectionProperties; // the active set of connection properties private boolean integratedSecurity = SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.getDefaultValue(); private AuthenticationScheme intAuthScheme = AuthenticationScheme.nativeAuthentication; + private GSSCredential ImpersonatedUserCred ; // This is the current connect place holder this should point one of the primary or failover place holder ServerPortPlaceHolder currentConnectPlaceHolder = null; @@ -1192,6 +1196,12 @@ Connection connectInternal(Properties propsIn, } } + if(intAuthScheme == AuthenticationScheme.javaKerberos){ + sPropKey = SQLServerDriverObjectProperty.GSS_CREDENTIAL.toString(); + if(activeConnectionProperties.containsKey(sPropKey)) + ImpersonatedUserCred = (GSSCredential) activeConnectionProperties.get(sPropKey); + } + sPropKey = SQLServerDriverStringProperty.AUTHENTICATION.toString(); sPropValue = activeConnectionProperties.getProperty(sPropKey); if (sPropValue == null) { @@ -2988,8 +2998,13 @@ final boolean doExecute() throws SQLServerException { SSPIAuthentication authentication = null; if (integratedSecurity && AuthenticationScheme.nativeAuthentication == intAuthScheme) authentication = new AuthenticationJNI(this, currentConnectPlaceHolder.getServerName(), currentConnectPlaceHolder.getPortNumber()); - if (integratedSecurity && AuthenticationScheme.javaKerberos == intAuthScheme) - authentication = new KerbAuthentication(this, currentConnectPlaceHolder.getServerName(), currentConnectPlaceHolder.getPortNumber()); + if (integratedSecurity && AuthenticationScheme.javaKerberos == intAuthScheme) { + if (null != ImpersonatedUserCred) + authentication = new KerbAuthentication(this, currentConnectPlaceHolder.getServerName(), currentConnectPlaceHolder.getPortNumber(), + ImpersonatedUserCred); + else + authentication = new KerbAuthentication(this, currentConnectPlaceHolder.getServerName(), currentConnectPlaceHolder.getPortNumber()); + } // If the workflow being used is Active Directory Password or Active Directory Integrated and server's prelogin response // for FEDAUTHREQUIRED option indicates Federated Authentication is required, we have to insert FedAuth Feature Extension @@ -3028,6 +3043,16 @@ final boolean doExecute() throws SQLServerException { if (null != authentication) authentication.ReleaseClientContext(); authentication = null; + + if (null != ImpersonatedUserCred) { + try { + ImpersonatedUserCred.dispose(); + } + catch (GSSException e) { + if (connectionlogger.isLoggable(Level.FINER)) + connectionlogger.finer(toString() + " Release of the credentials failed GSSException: " + e); + } + } } } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 8f21ee3ad..6d72c4f9c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -20,6 +20,8 @@ import java.util.logging.Level; import java.util.logging.Logger; +import org.ietf.jgss.GSSCredential; + /** * SQLServerDriver implements the java.sql.Driver for SQLServerConnect. * @@ -192,6 +194,30 @@ else if (value.equalsIgnoreCase(ApplicationIntent.READ_WRITE.toString())) { } } +enum SQLServerDriverObjectProperty { + GSS_CREDENTIAL("gsscredential", null); + private String name; + private Object defaultValue; + + private SQLServerDriverObjectProperty(String name, + Object defaultValue) { + this.name = name; + this.defaultValue = defaultValue; + } + + /** + * returning string due to structure of DRIVER_PROPERTIES_PROPERTY_ONLY + * @return + */ + public String getDefaultValue() { + return null; + } + + public String toString() { + return name; + } +} + enum SQLServerDriverStringProperty { APPLICATION_INTENT ("applicationIntent", ApplicationIntent.READ_WRITE.toString()), @@ -217,7 +243,8 @@ enum SQLServerDriverStringProperty KEY_STORE_AUTHENTICATION ("keyStoreAuthentication", ""), KEY_STORE_SECRET ("keyStoreSecret", ""), KEY_STORE_LOCATION ("keyStoreLocation", ""), - FIPS_PROVIDER ("fipsProvider", ""); + FIPS_PROVIDER ("fipsProvider", ""), + ; private String name; private String defaultValue; @@ -351,10 +378,11 @@ public final class SQLServerDriver implements java.sql.Driver { // Properties that can only be set by using Properties. // Cannot set in connection string private static final SQLServerDriverPropertyInfo[] DRIVER_PROPERTIES_PROPERTY_ONLY = { - // default required available choices - // property name value property (if appropriate) - new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.ACCESS_TOKEN.toString(), - SQLServerDriverStringProperty.ACCESS_TOKEN.getDefaultValue(), false, null),}; + // default required available choices + // property name value property (if appropriate) + new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.ACCESS_TOKEN.toString(), SQLServerDriverStringProperty.ACCESS_TOKEN.getDefaultValue(), false, null), + new SQLServerDriverPropertyInfo(SQLServerDriverObjectProperty.GSS_CREDENTIAL.toString(), SQLServerDriverObjectProperty.GSS_CREDENTIAL.getDefaultValue(), false, null), + }; private static final String driverPropertiesSynonyms[][] = { {"database", SQLServerDriverStringProperty.DATABASE_NAME.toString()}, @@ -421,6 +449,9 @@ static Properties fixupProperties(Properties props) throws SQLServerException { // replace with the driver approved name fixedup.setProperty(newname, val); } + else if(newname.equalsIgnoreCase("gsscredential") && (props.get(name) instanceof GSSCredential)){ + fixedup.put(newname, props.get(name)); + } else { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidpropertyValue")); Object[] msgArgs = {name}; @@ -441,9 +472,7 @@ static Properties mergeURLAndSuppliedProperties(Properties urlProps, return urlProps; if (suppliedProperties.isEmpty()) return urlProps; - Properties suppliedPropertiesFixed = fixupProperties(suppliedProperties); - // Merge URL properties and supplied properties. for (int i = 0; i < DRIVER_PROPERTIES.length; i++) { String sProp = DRIVER_PROPERTIES[i].getName(); @@ -457,10 +486,10 @@ static Properties mergeURLAndSuppliedProperties(Properties urlProps, // Merge URL properties with property-only properties for (int i = 0; i < DRIVER_PROPERTIES_PROPERTY_ONLY.length; i++) { String sProp = DRIVER_PROPERTIES_PROPERTY_ONLY[i].getName(); - String sPropVal = suppliedPropertiesFixed.getProperty(sProp); // supplied properties have precedence - if (null != sPropVal) { + Object oPropVal = suppliedPropertiesFixed.get(sProp); // supplied properties have precedence + if (null != oPropVal) { // overwrite the property in urlprops if already exists. supp prop has more precedence - urlProps.put(sProp, sPropVal); + urlProps.put(sProp, oPropVal); } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index c8c5a90c0..53d9ddf6c 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -188,6 +188,7 @@ protected Object[][] getContents() { {"R_TransparentNetworkIPResolutionPropertyDescription", "Determines whether to use the Transparent Network IP Resolution feature."}, {"R_queryTimeoutPropertyDescription", "The number of seconds to wait before the database reports a query time-out."}, {"R_socketTimeoutPropertyDescription", "The number of milliseconds to wait before the java.net.SocketTimeoutException is raised."}, + {"R_gsscredentialPropertyDescription", "Impersonated GSS Credential to access SQL Server."}, {"R_noParserSupport", "An error occurred while instantiating the required parser. Error: \"{0}\""}, {"R_writeOnlyXML", "Cannot read from this SQLXML instance. This instance is for writing data only."}, {"R_dataHasBeenReadXML", "Cannot read from this SQLXML instance. The data has already been read."}, From f253da1bdb512296940285617089ea4062e6929d Mon Sep 17 00:00:00 2001 From: Suraiya Hameed Date: Tue, 7 Mar 2017 17:19:14 -0800 Subject: [PATCH 2/2] datasource support --- .../sqlserver/jdbc/SQLServerDataSource.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index a8f0fbca1..01423753a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -22,6 +22,8 @@ import javax.naming.StringRefAddr; import javax.sql.DataSource; +import org.ietf.jgss.GSSCredential; + /** * This datasource lists properties specific for the SQLServerConnection class. */ @@ -175,6 +177,25 @@ public String getAuthentication() { SQLServerDriverStringProperty.AUTHENTICATION.getDefaultValue()); } + /** + * sets GSSCredential + * + * @param userCredential + */ + public void setGSSCredentials(GSSCredential userCredential){ + setObjectProperty(connectionProps,SQLServerDriverObjectProperty.GSS_CREDENTIAL.toString(), userCredential); + } + + /** + * Retrieves the GSSCredential + * + * @return GSSCredential + */ + public GSSCredential getGSSCredentials(){ + return (GSSCredential) getObjectProperty(connectionProps, SQLServerDriverObjectProperty.GSS_CREDENTIAL.toString(), + SQLServerDriverObjectProperty.GSS_CREDENTIAL.getDefaultValue()); + } + /** * Sets the access token. * @@ -772,6 +793,30 @@ private boolean getBooleanProperty(Properties props, return value.booleanValue(); } + private void setObjectProperty(Properties props, + String propKey, + Object propValue) { + if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) { + loggerExternal.entering(getClassNameLogging(), "set" + propKey); + } + if (null != propValue) { + props.put(propKey, propValue); + } + loggerExternal.exiting(getClassNameLogging(), "set" + propKey); + } + + private Object getObjectProperty(Properties props, + String propKey, + Object defaultValue) { + if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) + loggerExternal.entering(getClassNameLogging(), "get" + propKey); + Object propValue = props.get(propKey); + if (null == propValue) + propValue = defaultValue; + loggerExternal.exiting(getClassNameLogging(), "get" + propKey); + return propValue; + } + // Returns a SQLServerConnection given username, password, and pooledConnection. // Note that the DataSource properties set to connectionProps are used when creating // the connection.