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
/*
 * Microsoft JDBC Driver for SQL Server
 * 
 * Copyright(c) Microsoft Corporation All rights reserved.
 * 
 * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
 */

package com.microsoft.sqlserver.jdbc;

import java.net.IDN;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

import javax.security.auth.Subject;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.Oid;

/**
 * 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");
            // 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 (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);
                }

                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);
        }
    }

    /**
     * 
     * @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) {
            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