Skip to content

Commit

Permalink
Merge pull request #178 from v-suhame/ConstrainedDelegation
Browse files Browse the repository at this point in the history
Constrained delegation support [issue#79]
  • Loading branch information
AfsanehR-zz authored Mar 10, 2017
2 parents e4095e2 + f253da1 commit 9fc1b51
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 40 deletions.
82 changes: 54 additions & 28 deletions src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -505,6 +508,7 @@ static synchronized List<String> 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;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
Expand Down
49 changes: 39 additions & 10 deletions src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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()),
Expand All @@ -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;
Expand Down Expand Up @@ -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()},
Expand Down Expand Up @@ -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};
Expand All @@ -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();
Expand All @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."},
Expand Down

0 comments on commit 9fc1b51

Please sign in to comment.