All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.sqlserver.jdbc.KerbAuthentication Maven / Gradle / Ivy

There is a newer version: 12.8.1.jre11
Show newest version
//---------------------------------------------------------------------------------------------------------------------------------
// File: KerbAuthentication.java
//
//
// Microsoft JDBC Driver for SQL Server
// Copyright(c) Microsoft Corporation
// All rights reserved.
// MIT License
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""), 
//  to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
//  and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
//  IN THE SOFTWARE.
//---------------------------------------------------------------------------------------------------------------------------------
 
 
package com.microsoft.sqlserver.jdbc;
import java.util.logging.*;
import org.ietf.jgss.*;
import javax.security.auth.Subject;
import java.util.*;
import javax.security.auth.login.*;
import java.net.IDN;
import java.security.*;


/**
* KerbAuthentication for int auth.
*/
final class KerbAuthentication extends SSPIAuthentication
{
    private final static String CONFIGNAME = "SQLJDBCDriver";
    private final static java.util.logging.Logger authLogger 	= java.util.logging.Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.KerbAuthentication");

	private final SQLServerConnection con;
    private final String spn;

    private final GSSManager manager = GSSManager.getInstance();
    private LoginContext lc = null;    
    private GSSCredential peerCredentials = null;
	private GSSContext peerContext = null;

    
    static 
	{
	    // The driver on load will look to see if there is a configuration set for the SQLJDBCDriver, if not it will install its 
        // own configuration. Note it is possible that there is a configuration exists but it does not contain a configuration entry 
        // for the driver in that case, we will override the configuration but will flow the configuration requests to existing
        // config for anything other than SQLJDBCDriver
        // 
    	class SQLJDBCDriverConfig extends Configuration
    	{
    	    Configuration current = null;
            AppConfigurationEntry[] driverConf;

            SQLJDBCDriverConfig()
            {
                try 
                {
                     current = Configuration.getConfiguration();
                }
                catch (SecurityException e)
                {
                    // if we cant get the configuration, it is likely that no configuration has been specified. So go ahead and set the config
                    authLogger.finer(toString() + " No configurations provided, setting driver default");
                }
                AppConfigurationEntry[] config = null;
                
                if(null != current)
                {
                    config = current.getAppConfigurationEntry(CONFIGNAME);
                }
                // If there is user provided configuration we leave use that and not install our configuration
                if(null == config)
                {
                    if (authLogger.isLoggable(Level.FINER)) 
                        authLogger.finer(toString() + " SQLJDBCDriver configuration entry is not provided, setting driver default");
                    
                    AppConfigurationEntry appConf;
    				if(Util.isIBM())
                    {
                        Map confDetails = new HashMap();
        				confDetails.put("useDefaultCcache", "true");
                        confDetails.put("moduleBanner", "false");
                        appConf = new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,  confDetails);
                        if (authLogger.isLoggable(Level.FINER)) 
                            authLogger.finer(toString() + " Setting IBM Krb5LoginModule");    
                    }
                    else
                    {
                        Map confDetails = new HashMap();
        				confDetails.put("useTicketCache", "true");
        				confDetails.put("doNotPrompt", "true");
    				    appConf = new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,  confDetails);
                        if (authLogger.isLoggable(Level.FINER)) 
                            authLogger.finer(toString() + " Setting Sun Krb5LoginModule");    
                    }
    				driverConf = new AppConfigurationEntry[1];
    				driverConf[0] = appConf;
            		Configuration.setConfiguration(this);
                }
                
            }
            
    		public AppConfigurationEntry[] getAppConfigurationEntry(String name) 
    		{   
    		    // we should only handle anything that is related to our part, everything else is handled by the configuration 
    		    // already existing configuration if there is one. 
    		    if(name.equals(CONFIGNAME))
                {         
                    return driverConf;
                }
                else
                {
                    if(null != current)
                        return current.getAppConfigurationEntry(name);
                    else
                        return null;
                }
    		}
    		
    		public void refresh()
    		{
    			if(null != current)
                    current.refresh();
    		}
    	}
        SQLJDBCDriverConfig driverconfig = new SQLJDBCDriverConfig();
	}    
    
    private void intAuthInit() throws SQLServerException
	{
		try
		{
            // 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. 
            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");
    		}

            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) 
		{
		    authLogger.finer(toString() + "initAuthInit failed GSSException:-" + ge);

            con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), ge);            
		}
        catch(PrivilegedActionException ge) 
		{
		    authLogger.finer(toString() + "initAuthInit failed privileged exception:-" + ge);

            con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), ge);            
		}
        

	}

    // We have to do a privileged action to create the credential of the user in the current context
    private static GSSCredential getClientCredential(final Subject subject, final GSSManager MANAGER, final Oid kerboid)
            throws PrivilegedActionException 
    {
         final PrivilegedExceptionAction action = 
            new PrivilegedExceptionAction() {
                public GSSCredential run() throws GSSException {
                    return MANAGER.createCredential(
                        null // use the default principal
                        , GSSCredential.DEFAULT_LIFETIME
                        ,kerboid 
                        , GSSCredential.INITIATE_ONLY);
                } 
            };
        // TO support java 5, 6 we have to do this
        // 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;
    }
    private byte [] intAuthHandShake(byte[] pin,   boolean[] done) throws SQLServerException
    {
        try
        {
            if (authLogger.isLoggable(Level.FINER)) 
    		{
    			authLogger.finer(toString() + " Sending token to server over secure context");
    		}
			byte [] byteToken = peerContext.initSecContext(pin, 0,  pin.length);
            
	        if (peerContext.isEstablished())
            {
                done[0] = true;
                if (authLogger.isLoggable(Level.FINER)) 
                    authLogger.finer(toString() + "Authentication done.");
  		    }
            else
                if (null == byteToken)
                 {
                    // The documentation is not clear on when this can happen but it does say this could happen
                    authLogger.info(toString() + "byteToken is null in initSecContext.");
                    con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"));        
                 }
           return byteToken;
        }
        catch(GSSException ge) 
		{
		    authLogger.finer(toString() + "initSecContext Failed :-" + ge);

            con.terminate(SQLServerException.DRIVER_ERROR_NONE, SQLServerException.getErrString("R_integratedAuthenticationFailed"), ge);            
		}
        // keep the compiler happy
        return null;
    }
    
	private String makeSpn(String server, int port) throws SQLServerException
    {
		if (authLogger.isLoggable(Level.FINER)) 
		{
			authLogger.finer(toString() + " Server: " + server + " port: " +  port);
		}
        StringBuilder spn = new StringBuilder("MSSQLSvc/");
		//Format is MSSQLSvc/myhost.domain.company.com:1433
		// FQDN must be provided
        if(con.serverNameAsACE())
        {
        	spn.append(IDN.toASCII(server));
        }
        else
        {
        	spn.append(server);
        }		
		spn.append(":");
		spn.append(port);
		String strSPN = spn.toString();
		if (authLogger.isLoggable(Level.FINER)) 
		{
			authLogger.finer(toString() + " SPN: " + strSPN);
		}
		return strSPN;
	}

    // Package visible members below. 
	KerbAuthentication(SQLServerConnection con, String address, int port) throws SQLServerException
	{
		this.con = con;
		// Get user provided SPN string; if not provided then build the generic one
		String userSuppliedServerSpn = con.activeConnectionProperties.
				getProperty(SQLServerDriverStringProperty.SERVER_SPN.toString());
		
		if (null != userSuppliedServerSpn)
		{
			// serverNameAsACE is true, translate the user supplied serverSPN to ASCII
			if(con.serverNameAsACE())
			{
				int slashPos = userSuppliedServerSpn.indexOf("/");
				spn = userSuppliedServerSpn.substring(0,slashPos+1)
						+ IDN.toASCII(userSuppliedServerSpn.substring(slashPos+1));
			}
			else
			{
				spn = userSuppliedServerSpn;
			}
		}
        else
        {
        	spn =  makeSpn(address, port);
        }
	}
	
    byte[] GenerateClientContext(byte[] pin,   boolean[] done ) throws SQLServerException
    {
        if(null == peerContext)
        {
            intAuthInit();
        }
        return intAuthHandShake(pin, done);
    }
    int ReleaseClientContext() throws SQLServerException
    {
        try 
        {
            if(null != peerCredentials)
                peerCredentials.dispose();
            if(null != peerContext)
                peerContext.dispose();
            if(null != lc)
                lc.logout();
        }
        catch(LoginException e)
        {
            // yes we are eating exceptions here but this should not fail in the normal circumstances and we do not want to eat previous
            // login errors if caused before which is more useful to the user than the cleanup errors.
            authLogger.fine(toString() + " Release of the credentials failed LoginException: " + e);
        }
        catch(GSSException e)
        {
            // yes we are eating exceptions here but this should not fail in the normal circumstances and we do not want to eat previous
            // login errors if caused before which is more useful to the user than the cleanup errors.
            authLogger.fine(toString() + " Release of the credentials failed GSSException: " + e);
        }
        return 0;
    }
}







© 2015 - 2024 Weber Informatics LLC | Privacy Policy