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

jcifs.smb.Kerb5Context Maven / Gradle / Ivy

/*
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package jcifs.smb;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.Key;

import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosTicket;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
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.MessageProp;
import org.ietf.jgss.Oid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcifs.CIFSException;
import jcifs.spnego.NegTokenInit;


/**
 * This class used to provide Kerberos feature when setup GSSContext.
 * 
 * @author Shun
 */
class Kerb5Context implements SSPContext {

    private static final Logger log = LoggerFactory.getLogger(Kerb5Context.class);

    private static final ASN1ObjectIdentifier KRB5_MECH_OID;
    private static final ASN1ObjectIdentifier KRB5_MS_MECH_OID;
    static final ASN1ObjectIdentifier[] SUPPORTED_MECHS;
    private static final Oid JGSS_KRB5_NAME_OID;
    private static final Oid JGSS_KRB5_MECH_OID;

    static {
        KRB5_MECH_OID = new ASN1ObjectIdentifier("1.2.840.113554.1.2.2");
        KRB5_MS_MECH_OID = new ASN1ObjectIdentifier("1.2.840.48018.1.2.2");
        SUPPORTED_MECHS = new ASN1ObjectIdentifier[] {
            KRB5_MECH_OID, KRB5_MS_MECH_OID
        };

        Oid krbNameOid = null;
        Oid krbMechOid = null;
        try {
            krbNameOid = new Oid("1.2.840.113554.1.2.2.1");
            krbMechOid = new Oid("1.2.840.113554.1.2.2");
        }
        catch ( Exception e ) {
            log.error("Failed to initialize kerberos OIDs", e);
        }

        JGSS_KRB5_NAME_OID = krbNameOid;
        JGSS_KRB5_MECH_OID = krbMechOid;
    }

    private final GSSContext gssContext;
    private final GSSName clientName;
    private final GSSName serviceName;


    Kerb5Context ( String host, String service, String name, int userLifetime, int contextLifetime, String realm ) throws GSSException {
        GSSManager manager = GSSManager.getInstance();
        GSSCredential clientCreds = null;
        Oid mechOid = JGSS_KRB5_MECH_OID;
        if ( realm != null ) {
            this.serviceName = manager.createName(service + "/" + host + "@" + realm, JGSS_KRB5_NAME_OID, mechOid);
        }
        else {
            this.serviceName = manager.createName(service + "@" + host, GSSName.NT_HOSTBASED_SERVICE, mechOid);
        }

        if ( log.isDebugEnabled() ) {
            log.debug("Service name is " + this.serviceName);
        }

        if ( name != null ) {
            this.clientName = manager.createName(name, GSSName.NT_USER_NAME, mechOid);
            clientCreds = manager.createCredential(this.clientName, userLifetime, mechOid, GSSCredential.INITIATE_ONLY);
        }
        else {
            this.clientName = null;
        }

        this.gssContext = manager.createContext(this.serviceName, mechOid, clientCreds, contextLifetime);

        this.gssContext.requestAnonymity(false);
        this.gssContext.requestSequenceDet(false);
        this.gssContext.requestConf(false);
        this.gssContext.requestInteg(false);
        this.gssContext.requestReplayDet(false);

        // per spec these should be set
        this.gssContext.requestMutualAuth(true);
        this.gssContext.requestCredDeleg(true);
    }


    /**
     * 
     * {@inheritDoc}
     *
     * @see jcifs.smb.SSPContext#isSupported(org.bouncycastle.asn1.ASN1ObjectIdentifier)
     */
    @Override
    public boolean isSupported ( ASN1ObjectIdentifier mechanism ) {
        return KRB5_MECH_OID.equals(mechanism) || KRB5_MS_MECH_OID.equals(mechanism);
    }


    /**
     * {@inheritDoc}
     *
     * @see jcifs.smb.SSPContext#isPreferredMech(org.bouncycastle.asn1.ASN1ObjectIdentifier)
     */
    @Override
    public boolean isPreferredMech ( ASN1ObjectIdentifier mechanism ) {
        return isSupported(mechanism);
    }


    /**
     * {@inheritDoc}
     *
     * @see jcifs.smb.SSPContext#getSupportedMechs()
     */
    @Override
    public ASN1ObjectIdentifier[] getSupportedMechs () {
        return SUPPORTED_MECHS;
    }


    /**
     * {@inheritDoc}
     *
     * @see jcifs.smb.SSPContext#getFlags()
     */
    @Override
    public int getFlags () {
        int contextFlags = 0;
        if ( this.gssContext.getCredDelegState() ) {
            contextFlags |= NegTokenInit.DELEGATION;
        }
        if ( this.gssContext.getMutualAuthState() ) {
            contextFlags |= NegTokenInit.MUTUAL_AUTHENTICATION;
        }
        if ( this.gssContext.getReplayDetState() ) {
            contextFlags |= NegTokenInit.REPLAY_DETECTION;
        }
        if ( this.gssContext.getSequenceDetState() ) {
            contextFlags |= NegTokenInit.SEQUENCE_CHECKING;
        }
        if ( this.gssContext.getAnonymityState() ) {
            contextFlags |= NegTokenInit.ANONYMITY;
        }
        if ( this.gssContext.getConfState() ) {
            contextFlags |= NegTokenInit.CONFIDENTIALITY;
        }
        if ( this.gssContext.getIntegState() ) {
            contextFlags |= NegTokenInit.INTEGRITY;
        }
        return contextFlags;
    }


    @Override
    public boolean isEstablished () {
        return this.gssContext != null && this.gssContext.isEstablished();
    }


    /**
     * {@inheritDoc}
     *
     * @see jcifs.smb.SSPContext#supportsIntegrity()
     */
    @Override
    public boolean supportsIntegrity () {
        return true;
    }


    /**
     * {@inheritDoc}
     * 
     * @throws CIFSException
     *
     * @see jcifs.smb.SSPContext#calculateMIC(byte[])
     */
    @Override
    public byte[] calculateMIC ( byte[] data ) throws CIFSException {
        try {
            return this.gssContext.getMIC(data, 0, data.length, new MessageProp(false));
        }
        catch ( GSSException e ) {
            throw new CIFSException("Failed to calculate MIC", e);
        }
    }


    /**
     * {@inheritDoc}
     *
     * @see jcifs.smb.SSPContext#verifyMIC(byte[], byte[])
     */
    @Override
    public void verifyMIC ( byte[] data, byte[] mic ) throws CIFSException {
        try {
            this.gssContext.verifyMIC(mic, 0, mic.length, data, 0, data.length, new MessageProp(false));
        }
        catch ( GSSException e ) {
            throw new CIFSException("Failed to verify MIC", e);
        }
    }


    /**
     * {@inheritDoc}
     *
     * @see jcifs.smb.SSPContext#isMICAvailable()
     */
    @Override
    public boolean isMICAvailable () {
        return this.gssContext.getIntegState();
    }


    /**
     * {@inheritDoc}
     *
     * @see jcifs.smb.SSPContext#getNetbiosName()
     */
    @Override
    public String getNetbiosName () {
        return null;
    }


    /**
     * {@inheritDoc}
     *
     * @see jcifs.smb.SSPContext#getSigningKey()
     */
    @Override
    public byte[] getSigningKey () throws SmbException {
        /*
         * The kerberos session key is not accessible via the JGSS API. IBM and
         * Oracle both implement a similar API to make an ExtendedGSSContext
         * available. That API is accessed via reflection to make this independent
         * of the runtime JRE
         */
        if ( EXT_GSS_CONTEXT_CLASS == null || INQUIRE_SEC_CONTEXT == null || INQUIRE_TYPE_SESSION_KEY == null ) {
            throw new SmbException("ExtendedGSSContext support not available from JRE");
        }
        else if ( EXT_GSS_CONTEXT_CLASS.isAssignableFrom(this.gssContext.getClass()) ) {
            try {
                Key k = (Key) INQUIRE_SEC_CONTEXT.invoke(this.gssContext, new Object[] {
                    INQUIRE_TYPE_SESSION_KEY
                });
                return k.getEncoded();
            }
            catch (
                IllegalAccessException |
                IllegalArgumentException |
                InvocationTargetException ex ) {
                throw new SmbException("Failed to query Kerberos session key from ExtendedGSSContext", ex);
            }
        }
        throw new SmbException("ExtendedGSSContext is not implemented by GSSContext");
    }


    @Override
    public byte[] initSecContext ( byte[] token, int off, int len ) throws SmbException {
        try {
            return this.gssContext.initSecContext(token, off, len);
        }
        catch ( GSSException e ) {
            throw new SmbAuthException("GSSAPI mechanism failed", e);
        }
    }


    Key searchSessionKey ( Subject subject ) throws GSSException {
        MIEName src = new MIEName(this.gssContext.getSrcName().export());
        MIEName targ = new MIEName(this.gssContext.getTargName().export());

        ASN1ObjectIdentifier mech = ASN1ObjectIdentifier.getInstance(this.gssContext.getMech().getDER());
        for ( KerberosTicket ticket : subject.getPrivateCredentials(KerberosTicket.class) ) {
            MIEName client = new MIEName(mech, ticket.getClient().getName());
            MIEName server = new MIEName(mech, ticket.getServer().getName());
            if ( src.equals(client) && targ.equals(server) ) {
                return ticket.getSessionKey();
            }
        }
        return null;
    }


    /**
     * {@inheritDoc}
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString () {
        if ( this.gssContext == null || !this.gssContext.isEstablished() ) {
            return String.format("KERB5[src=%s,targ=%s]", this.clientName, this.serviceName);
        }
        try {
            return String
                    .format("KERB5[src=%s,targ=%s,mech=%s]", this.gssContext.getSrcName(), this.gssContext.getTargName(), this.gssContext.getMech());
        }
        catch ( GSSException e ) {
            log.debug("Failed to get info", e);
            return super.toString();
        }
    }


    @Override
    public void dispose () throws SmbException {
        if ( this.gssContext != null ) {
            try {
                this.gssContext.dispose();
            }
            catch ( GSSException e ) {
                throw new SmbException("Context disposal failed", e);
            }
        }
    }

    /*
     * Prepare reflective access to ExtendedGSSContext. The reflective access
     * abstracts the access so far, that Oracle JDK, Open JDK and IBM JDK are
     * supported.
     * 
     * At the time of the first implementation only a test on Oracle JDK was
     * done.
     */

    private static final String OPENJDK_JGSS_INQUIRE_TYPE_CLASS = "com.sun.security.jgss.InquireType";
    private static final String OPENJDK_JGSS_EXT_GSSCTX_CLASS = "com.sun.security.jgss.ExtendedGSSContext";

    private static final String IBM_JGSS_INQUIRE_TYPE_CLASS = "com.ibm.security.jgss.InquireType";
    private static final String IBM_JGSS_EXT_GSSCTX_CLASS = "com.ibm.security.jgss.ExtendedGSSContext";

    private static final Class EXT_GSS_CONTEXT_CLASS;
    private static final Method INQUIRE_SEC_CONTEXT;
    private static final Object INQUIRE_TYPE_SESSION_KEY;

    static {
        Class extendedGSSContextClassPrep = null;
        Method inquireSecContextPrep = null;
        Object inquireTypeSessionKeyPrep = null;

        try {
            extendedGSSContextClassPrep = Class.forName(OPENJDK_JGSS_EXT_GSSCTX_CLASS);
            Class inquireTypeClass = Class.forName(OPENJDK_JGSS_INQUIRE_TYPE_CLASS);
            inquireTypeSessionKeyPrep = getSessionKeyInquireType(inquireTypeClass);
            inquireSecContextPrep = extendedGSSContextClassPrep.getMethod("inquireSecContext", inquireTypeClass);
        }
        catch (
            ClassNotFoundException |
            NoSuchMethodException |
            RuntimeException ex ) {
            if ( log.isDebugEnabled() ) {
                log.debug("Failed to initalize ExtendedGSSContext initializdation for OracleJDK / OpenJDK", ex);
            }
        }

        if ( extendedGSSContextClassPrep == null || inquireSecContextPrep == null || inquireTypeSessionKeyPrep == null ) {
            try {
                extendedGSSContextClassPrep = Class.forName(IBM_JGSS_EXT_GSSCTX_CLASS);
                Class inquireTypeClass = Class.forName(IBM_JGSS_INQUIRE_TYPE_CLASS);
                inquireTypeSessionKeyPrep = getSessionKeyInquireType(inquireTypeClass);
                inquireSecContextPrep = extendedGSSContextClassPrep.getMethod("inquireSecContext", inquireTypeClass);
            }
            catch (
                ClassNotFoundException |
                NoSuchMethodException |
                RuntimeException ex ) {
                if ( log.isDebugEnabled() ) {
                    log.debug("Failed to initalize ExtendedGSSContext initializdation for IBM JDK", ex);
                }
            }
        }
        EXT_GSS_CONTEXT_CLASS = extendedGSSContextClassPrep;
        INQUIRE_SEC_CONTEXT = inquireSecContextPrep;
        INQUIRE_TYPE_SESSION_KEY = inquireTypeSessionKeyPrep;

        if ( EXT_GSS_CONTEXT_CLASS != null && INQUIRE_SEC_CONTEXT != null && INQUIRE_TYPE_SESSION_KEY != null ) {
            if ( log.isDebugEnabled() ) {
                log.debug("Found ExtendedGSSContext implementation: " + EXT_GSS_CONTEXT_CLASS.getName());
            }
        }
    }


    @SuppressWarnings ( "unchecked" )
    private static > Object getSessionKeyInquireType ( Class inquireTypeClass ) {
        return Enum.valueOf((Class) inquireTypeClass, "KRB5_GET_SESSION_KEY");
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy