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

jcifs.smb.Kerb5Authenticator 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.io.IOException;
import java.security.Principal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;

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

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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


/**
 * Base kerberos authenticator
 * 
 * Uses a subject that contains kerberos credentials for use in GSSAPI context establishment.
 * 
 * Be advised that short/NetBIOS name usage is not supported with this authenticator. Always specify full FQDNs.
 * This can be a problem if using DFS in it's default configuration as they still return referrals in short form.
 * See KB-244380 for compatible server configuration.
 * See {@link jcifs.Configuration#isDfsConvertToFQDN()} for a workaround.
 */
public class Kerb5Authenticator extends NtlmPasswordAuthenticator {

    private static final long serialVersionUID = 1999400043787454432L;
    private static final Logger log = LoggerFactory.getLogger(Kerb5Authenticator.class);
    private static final String DEFAULT_SERVICE = "cifs";

    private static final Set PREFERRED_MECHS = new HashSet<>();

    private Subject subject = null;
    private String user = null;
    private String realm = null;
    private String service = DEFAULT_SERVICE;
    private int userLifetime = GSSCredential.DEFAULT_LIFETIME;
    private int contextLifetime = GSSContext.DEFAULT_LIFETIME;
    private boolean canFallback = false;
    private boolean forceFallback;

    static {
        PREFERRED_MECHS.add(new ASN1ObjectIdentifier("1.2.840.113554.1.2.2"));
        PREFERRED_MECHS.add(new ASN1ObjectIdentifier("1.2.840.48018.1.2.2"));
    }


    /**
     * Construct a Kerb5Authenticator object with Subject
     * which hold TGT retrieved from KDC. If multiple TGT are contained, the
     * first one will be used to retrieve user principal.
     * 
     * @param subject
     *            represents the user who perform Kerberos authentication.
     *            It contains tickets retrieve from KDC.
     */
    public Kerb5Authenticator ( Subject subject ) {
        this.subject = subject;
    }


    /**
     * Construct a Kerb5Authenticator object with Subject and
     * potential NTLM fallback (if the server does not support kerberos).
     * 
     * @param subject
     *            represents the user who perform Kerberos authentication. Should at least contain a TGT for the user.
     * @param domain
     *            domain for NTLM fallback
     * @param username
     *            user for NTLM fallback
     * @param password
     *            password for NTLM fallback
     */
    public Kerb5Authenticator ( Subject subject, String domain, String username, String password ) {
        super(domain, username, password);
        this.canFallback = true;
        this.subject = subject;
    }


    /**
     * Testing only: force fallback to NTLM
     * 
     * @param forceFallback
     *            the forceFallback to set
     */
    public void setForceFallback ( boolean forceFallback ) {
        this.forceFallback = forceFallback;
    }


    /**
     * 
     * {@inheritDoc}
     *
     * @see jcifs.smb.NtlmPasswordAuthenticator#createContext(jcifs.CIFSContext, java.lang.String, java.lang.String,
     *      byte[], boolean)
     */
    @Override
    public SSPContext createContext ( CIFSContext tc, String targetDomain, String host, byte[] initialToken, boolean doSigning ) throws SmbException {
        if ( host.indexOf('.') < 0 && host.toUpperCase(Locale.ROOT).equals(host) ) {
            // this is not too good, probably should better pass the address and check that it is a netbios one.
            // While we could look up the domain controller/KDC we cannot really make the java kerberos implementation
            // use a KDC of our choice.
            // A potential workaround would be to try to get the server FQDN by reverse lookup, but this might have
            // security implications and also is not how Microsoft does it.
            throw new SmbUnsupportedOperationException("Cannot use netbios/short names with kerberos authentication, have " + host);
        }
        try {
            NegTokenInit tok = new NegTokenInit(initialToken);
            if ( log.isDebugEnabled() ) {
                log.debug("Have initial token " + tok);
            }
            if ( tok.getMechanisms() != null ) {
                Set mechs = new HashSet<>(Arrays.asList(tok.getMechanisms()));
                boolean foundKerberos = false;
                for ( ASN1ObjectIdentifier mech : Kerb5Context.SUPPORTED_MECHS ) {
                    foundKerberos |= mechs.contains(mech);
                }
                if ( ( !foundKerberos || this.forceFallback ) && this.canFallback && tc.getConfig().isAllowNTLMFallback() ) {
                    log.debug("Falling back to NTLM authentication");
                    return super.createContext(tc, targetDomain, host, initialToken, doSigning);
                }
                else if ( !foundKerberos ) {
                    throw new SmbUnsupportedOperationException("Server does not support kerberos authentication");
                }
            }
        }
        catch ( SmbException e ) {
            throw e;
        }
        catch ( IOException e1 ) {
            log.debug("Ignoring invalid initial token", e1);
        }

        try {
            return createContext(tc, targetDomain, host);
        }
        catch ( GSSException e ) {
            throw new SmbException("Context setup failed", e);
        }
    }


    /**
     * @param subject
     *            the subject to set
     */
    protected void setSubject ( Subject subject ) {
        this.subject = subject;
    }


    @Override
    public void refresh () throws CIFSException {
        // custom Kerb5Authenticators need to override this method for support
        throw new SmbUnsupportedOperationException("Refreshing credentials is not supported by this authenticator");
    }


    @Override
    public Kerb5Authenticator clone () {
        Kerb5Authenticator auth = new Kerb5Authenticator(getSubject());
        cloneInternal(auth, this);
        return auth;
    }


    /**
     * Clone the context
     * 
     * @param to
     * @param from
     */
    public static void cloneInternal ( Kerb5Authenticator to, Kerb5Authenticator from ) {
        NtlmPasswordAuthenticator.cloneInternal(to, from);
        to.setUser(from.getUser());
        to.setRealm(from.getRealm());
        to.setService(from.getService());
        to.setLifeTime(from.getLifeTime());
        to.setUserLifeTime(from.getUserLifeTime());

        to.canFallback = from.canFallback;
        to.forceFallback = from.forceFallback;
    }


    /**
     * Set the user name which is used to setup GSSContext. If null
     * is set, the default user will be used which is retrieved from the first
     * TGT found in Subject .
     *
     * @param name
     *            the user name used to setup GSSContext
     */
    public void setUser ( String name ) {
        this.user = name;
    }


    /**
     * @param realm
     *            the realm to set
     */
    public void setRealm ( String realm ) {
        this.realm = realm;
    }


    /**
     * 
     * @return the kerberos realm
     */
    public String getRealm () {
        return this.realm;
    }


    /**
     * Get the Subject object.
     *
     * @return Subject represents the user who perform Kerberos authentication.
     *         It contains the tickets retrieve from KDC.
     */
    @Override
    public Subject getSubject () {
        return this.subject;
    }


    /**
     * Get the user name which authenticate against to. If the default user
     * is used, Null will be returned.
     *
     * @return user name
     */
    public String getUser () {
        return this.user;
    }


    /**
     * Set the service name which is used to setup GSSContext.
     * Program will use this name to require service ticket from KDC.
     *
     * @param name
     *            the service name used to require service ticket from KDC.
     */
    public void setService ( String name ) {
        this.service = name;
    }


    /**
     * Get the service name.
     *
     * @return the service name used to require service ticket from KDC
     */
    public String getService () {
        return this.service;
    }


    /**
     * Get lifetime of current user.
     *
     * @return the remaining lifetime in seconds. If the default lifetime is
     *         used, this value have no meaning.
     *
     */
    public int getUserLifeTime () {
        return this.userLifetime;
    }


    /**
     * Set lifetime of current user.
     *
     * @param time
     *            the lifetime in seconds
     *
     */
    public void setUserLifeTime ( int time ) {
        this.userLifetime = time;
    }


    /**
     * Get lifetime of this context.
     *
     * @return the remaining lifetime in seconds. If the default lifetime is
     *         used, this value have no meaning.
     */
    public int getLifeTime () {
        return this.contextLifetime;
    }


    /**
     * Set the lifetime for this context.
     *
     * @param time
     *            the lifetime in seconds
     */
    public void setLifeTime ( int time ) {
        this.contextLifetime = time;
    }


    /**
     * {@inheritDoc}
     *
     * @see jcifs.smb.CredentialsInternal#isAnonymous()
     */
    @Override
    public boolean isAnonymous () {
        return this.getSubject() == null && super.isAnonymous();
    }


    /**
     * {@inheritDoc}
     *
     * @see jcifs.smb.NtlmPasswordAuthenticator#isPreferredMech(org.bouncycastle.asn1.ASN1ObjectIdentifier)
     */
    @Override
    public boolean isPreferredMech ( ASN1ObjectIdentifier mechanism ) {
        if ( isAnonymous() ) {
            return super.isPreferredMech(mechanism);
        }
        return PREFERRED_MECHS.contains(mechanism);
    }


    /**
     * {@inheritDoc}
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString () {
        return "Kerb5Authenticatior[subject=" + ( this.getSubject() != null ? this.getSubject().getPrincipals() : null ) + ",user=" + this.user
                + ",realm=" + this.realm + "]";
    }


    private SpnegoContext createContext ( CIFSContext tc, String targetDomain, String host ) throws GSSException {
        return new SpnegoContext(
            tc.getConfig(),
            new Kerb5Context(
                host,
                this.service,
                this.user,
                this.userLifetime,
                this.contextLifetime,
                targetDomain != null ? targetDomain.toUpperCase(Locale.ROOT) : null));
    }


    /**
     * 
     * {@inheritDoc}
     *
     * @see jcifs.smb.NtlmPasswordAuthenticator#equals(java.lang.Object)
     */
    @Override
    public boolean equals ( Object other ) {
        // this method is called from SmbSession
        if ( other != null && other instanceof Kerb5Authenticator )
            return Objects.equals(this.getSubject(), ( (Kerb5Authenticator) other ).getSubject());

        return false;
    }


    /**
     * {@inheritDoc}
     *
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode () {
        return super.hashCode();
    }


    @Override
    public String getUserDomain () {
        if ( this.realm == null && this.getSubject() != null ) {
            Set pr = this.getSubject().getPrincipals();
            for ( Iterator ite = pr.iterator(); ite.hasNext(); ) {
                try {
                    KerberosPrincipal entry = (KerberosPrincipal) ite.next();
                    return entry.getRealm();
                }
                catch ( Exception e ) {
                    continue;
                }
            }
        }

        if ( this.realm != null ) {
            return this.realm;
        }

        return super.getUserDomain();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy