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

jcifs.smb.NtlmPasswordAuthenticator Maven / Gradle / Ivy

Go to download

JCIFS is an Open Source client library that implements the CIFS/SMB networking protocol in 100% Java

There is a newer version: 2.1.37
Show newest version
/*
 * 
 * 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.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.Principal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import javax.security.auth.Subject;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcifs.CIFSContext;
import jcifs.CIFSException;
import jcifs.Credentials;
import jcifs.RuntimeCIFSException;
import jcifs.spnego.NegTokenInit;
import jcifs.util.Crypto;
import jcifs.util.Strings;


/**
 * This class stores and encrypts NTLM user credentials.
 * 
 * Contrary to {@link NtlmPasswordAuthentication} this does not cause guest authentication
 * when the "guest" username is supplied. Use {@link AuthenticationType} instead.
 * 
 * @author mbechler
 */
@SuppressWarnings ( "javadoc" )
public class NtlmPasswordAuthenticator implements Principal, CredentialsInternal, Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = -4090263879887877186L;

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

    private AuthenticationType type;
    private String domain;
    private String username;
    private String password;
    private byte[] clientChallenge = null;


    /**
     * Construct anonymous credentials
     */
    public NtlmPasswordAuthenticator () {
        this(AuthenticationType.NULL);
    }


    public NtlmPasswordAuthenticator ( AuthenticationType type ) {
        this.domain = "";
        this.username = "";
        this.password = "";
        this.type = type;
    }


    /**
     * Create username/password credentials
     * 
     * @param username
     * @param password
     */
    public NtlmPasswordAuthenticator ( String username, String password ) {
        this(null, username, password);
    }


    /**
     * Create username/password credentials with specified domain
     * 
     * @param domain
     * @param username
     * @param password
     */
    public NtlmPasswordAuthenticator ( String domain, String username, String password ) {
        this(domain, username, password, AuthenticationType.USER);
    }


    /**
     * Create username/password credentials with specified domain
     * 
     * @param domain
     * @param username
     * @param password
     * @param type
     *            authentication type
     */
    public NtlmPasswordAuthenticator ( String domain, String username, String password, AuthenticationType type ) {
        if ( username != null ) {
            int ci = username.indexOf('@');
            if ( ci > 0 ) {
                domain = username.substring(ci + 1);
                username = username.substring(0, ci);
            }
            else {
                ci = username.indexOf('\\');
                if ( ci > 0 ) {
                    domain = username.substring(0, ci);
                    username = username.substring(ci + 1);
                }
            }
        }

        this.domain = domain != null ? domain : "";
        this.username = username != null ? username : "";
        this.password = password != null ? password : "";
        if ( type == null ) {
            this.type = guessAuthenticationType();
        }
        else {
            this.type = type;
        }
    }


    protected NtlmPasswordAuthenticator ( String userInfo, String defDomain, String defUser, String defPassword ) {
        this(userInfo, defDomain, defUser, defPassword, null);
    }


    /**
     * @param userInfo
     */
    protected NtlmPasswordAuthenticator ( String userInfo, String defDomain, String defUser, String defPassword, AuthenticationType type ) {
        String dom = null, user = null, pass = null;
        if ( userInfo != null ) {
            try {
                userInfo = unescape(userInfo);
            }
            catch ( UnsupportedEncodingException uee ) {
                throw new RuntimeCIFSException(uee);
            }
            int i, u;
            int end = userInfo.length();
            for ( i = 0, u = 0; i < end; i++ ) {
                char c = userInfo.charAt(i);
                if ( c == ';' ) {
                    dom = userInfo.substring(0, i);
                    u = i + 1;
                }
                else if ( c == ':' ) {
                    pass = userInfo.substring(i + 1);
                    break;
                }
            }
            user = userInfo.substring(u, i);
        }

        this.domain = dom != null ? dom : ( defDomain != null ? defDomain : "" );
        this.username = user != null ? user : ( defUser != null ? defUser : "" );
        this.password = pass != null ? pass : ( defPassword != null ? defPassword : "" );

        if ( type == null ) {
            this.type = guessAuthenticationType();
        }
        else {
            this.type = type;
        }
    }


    /**
     * @return
     */
    protected AuthenticationType guessAuthenticationType () {
        AuthenticationType t = AuthenticationType.USER;
        if ( "guest".equalsIgnoreCase(this.username) ) {
            t = AuthenticationType.GUEST;
        }
        else if ( ( getUserDomain() == null || getUserDomain().isEmpty() ) && getUsername().isEmpty() && ( getPassword().isEmpty() ) ) {
            t = AuthenticationType.NULL;
        }
        return t;
    }


    @SuppressWarnings ( "unchecked" )
    @Override
    public  T unwrap ( Class t ) {
        if ( t.isAssignableFrom(this.getClass()) ) {
            return (T) this;
        }
        return null;
    }


    @Override
    public Subject getSubject () {
        return null;
    }


    @Override
    public void refresh () throws CIFSException {}


    /**
     * 
     * {@inheritDoc}
     *
     * @see jcifs.smb.CredentialsInternal#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 ( tc.getConfig().isUseRawNTLM() ) {
            return setupTargetName(tc, host, new NtlmContext(tc, this, doSigning));
        }

        try {
            if ( initialToken != null && initialToken.length > 0 ) {
                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()));
                    if ( !mechs.contains(NtlmContext.NTLMSSP_OID) ) {
                        throw new SmbUnsupportedOperationException("Server does not support NTLM authentication");
                    }
                }
            }
        }
        catch ( SmbException e ) {
            throw e;
        }
        catch ( IOException e1 ) {
            log.debug("Ignoring invalid initial token", e1);
        }

        return new SpnegoContext(tc.getConfig(), setupTargetName(tc, host, new NtlmContext(tc, this, doSigning)));
    }


    private static SSPContext setupTargetName ( CIFSContext tc, String host, NtlmContext ntlmContext ) {
        if ( host != null && tc.getConfig().isSendNTLMTargetName() ) {
            ntlmContext.setTargetName(String.format("cifs/%s", host));
        }
        return ntlmContext;
    }


    @Override
    public NtlmPasswordAuthenticator clone () {
        NtlmPasswordAuthenticator cloned = new NtlmPasswordAuthenticator();
        cloneInternal(cloned, this);
        return cloned;
    }


    protected static void cloneInternal ( NtlmPasswordAuthenticator cloned, NtlmPasswordAuthenticator toClone ) {
        cloned.domain = toClone.domain;
        cloned.username = toClone.username;
        cloned.password = toClone.password;
        cloned.type = toClone.type;
    }


    /**
     * Returns the domain.
     */
    @Override
    public String getUserDomain () {
        return this.domain;
    }


    /**
     * 
     * @return the original specified user domain
     */
    public String getSpecifiedUserDomain () {
        return this.domain;
    }


    /**
     * Returns the username.
     * 
     * @return the username
     */
    public String getUsername () {
        return this.username;
    }


    /**
     * Returns the password in plain text or null if the raw password
     * hashes were used to construct this NtlmPasswordAuthentication
     * object which will be the case when NTLM HTTP Authentication is
     * used. There is no way to retrieve a users password in plain text unless
     * it is supplied by the user at runtime.
     * 
     * @return the password
     */
    public String getPassword () {
        return this.password;
    }


    /**
     * Return the domain and username in the format:
     * domain\\username. This is equivalent to toString().
     */
    @Override
    public String getName () {
        boolean d = this.domain != null && this.domain.length() > 0;
        return d ? this.domain + "\\" + this.username : this.username;
    }


    /**
     * Compares two NtlmPasswordAuthentication objects for equality.
     * 
     * Two NtlmPasswordAuthentication objects are equal if their caseless domain and username fields are equal
     *
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals ( Object obj ) {
        if ( obj instanceof NtlmPasswordAuthenticator ) {
            NtlmPasswordAuthenticator ntlm = (NtlmPasswordAuthenticator) obj;
            String domA = ntlm.getUserDomain() != null ? ntlm.getUserDomain().toUpperCase() : null;
            String domB = this.getUserDomain() != null ? this.getUserDomain().toUpperCase() : null;
            return ntlm.type == this.type && Objects.equals(domA, domB) && ntlm.getUsername().equalsIgnoreCase(this.getUsername())
                    && Objects.equals(getPassword(), ntlm.getPassword());
        }
        return false;
    }


    /**
     * Return the upcased username hash code.
     */
    @Override
    public int hashCode () {
        return getName().toUpperCase().hashCode();
    }


    /**
     * Return the domain and username in the format:
     * domain\\username. This is equivalent to getName().
     */
    @Override
    public String toString () {
        return getName();
    }


    @Override
    public boolean isAnonymous () {
        return this.type == AuthenticationType.NULL;
    }


    @Override
    public boolean isGuest () {
        return this.type == AuthenticationType.GUEST;
    }


    static String unescape ( String str ) throws NumberFormatException, UnsupportedEncodingException {
        char ch;
        int i, j, state, len;
        char[] out;
        byte[] b = new byte[1];

        if ( str == null ) {
            return null;
        }

        len = str.length();
        out = new char[len];
        state = 0;
        for ( i = j = 0; i < len; i++ ) {
            switch ( state ) {
            case 0:
                ch = str.charAt(i);
                if ( ch == '%' ) {
                    state = 1;
                }
                else {
                    out[ j++ ] = ch;
                }
                break;
            case 1:
                /*
                 * Get ASCII hex value and convert to platform dependent
                 * encoding like EBCDIC perhaps
                 */
                b[ 0 ] = (byte) ( Integer.parseInt(str.substring(i, i + 2), 16) & 0xFF );
                out[ j++ ] = ( new String(b, 0, 1, "ASCII") ).charAt(0);
                i++;
                state = 0;
            }
        }

        return new String(out, 0, j);
    }


    /**
     * @param mechanism
     * @return whether the given mechanism is the preferred one for this credential
     */
    public boolean isPreferredMech ( ASN1ObjectIdentifier mechanism ) {
        return NtlmContext.NTLMSSP_OID.equals(mechanism);
    }


    /**
     * Computes the 24 byte ANSI password hash given the 8 byte server challenge.
     * 
     * @param tc
     * @param chlng
     * @return the hash for the given challenge
     * @throws GeneralSecurityException
     */
    public byte[] getAnsiHash ( CIFSContext tc, byte[] chlng ) throws GeneralSecurityException {
        switch ( tc.getConfig().getLanManCompatibility() ) {
        case 0:
        case 1:
            return NtlmUtil.getPreNTLMResponse(tc, this.password, chlng);
        case 2:
            return NtlmUtil.getNTLMResponse(this.password, chlng);
        case 3:
        case 4:
        case 5:
            if ( this.clientChallenge == null ) {
                this.clientChallenge = new byte[8];
                tc.getConfig().getRandom().nextBytes(this.clientChallenge);
            }
            return NtlmUtil.getLMv2Response(this.domain, this.username, this.password, chlng, this.clientChallenge);
        default:
            return NtlmUtil.getPreNTLMResponse(tc, this.password, chlng);
        }
    }


    /**
     * Computes the 24 byte Unicode password hash given the 8 byte server challenge.
     * 
     * @param tc
     * @param chlng
     * @return the hash for the given challenge
     * @throws GeneralSecurityException
     */
    public byte[] getUnicodeHash ( CIFSContext tc, byte[] chlng ) throws GeneralSecurityException {
        switch ( tc.getConfig().getLanManCompatibility() ) {
        case 0:
        case 1:
        case 2:
            return NtlmUtil.getNTLMResponse(this.password, chlng);
        case 3:
        case 4:
        case 5:
            return new byte[0];
        default:
            return NtlmUtil.getNTLMResponse(this.password, chlng);
        }
    }


    /**
     * @param tc
     * @param chlng
     * @return the signing key
     * @throws SmbException
     * @throws GeneralSecurityException
     */
    public byte[] getSigningKey ( CIFSContext tc, byte[] chlng ) throws SmbException, GeneralSecurityException {
        switch ( tc.getConfig().getLanManCompatibility() ) {
        case 0:
        case 1:
        case 2:
            byte[] signingKey = new byte[40];
            getUserSessionKey(tc, chlng, signingKey, 0);
            System.arraycopy(getUnicodeHash(tc, chlng), 0, signingKey, 16, 24);
            return signingKey;
        case 3:
        case 4:
        case 5:
            /*
             * This code is only called if extended security is not on. This will
             * all be cleaned up an normalized in JCIFS 2.x.
             */
            throw new SmbException(
                "NTLMv2 requires extended security (jcifs.smb.client.useExtendedSecurity must be true if jcifs.smb.lmCompatibility >= 3)");
        }
        return null;
    }


    /**
     * Returns the effective user session key.
     * 
     * @param tc
     * @param chlng
     *            The server challenge.
     * @return A byte[] containing the effective user session key,
     *         used in SMB MAC signing and NTLMSSP signing and sealing.
     */
    public byte[] getUserSessionKey ( CIFSContext tc, byte[] chlng ) {
        byte[] key = new byte[16];
        try {
            getUserSessionKey(tc, chlng, key, 0);
        }
        catch ( Exception ex ) {
            log.error("Failed to get session key", ex);
        }
        return key;
    }


    /**
     * Calculates the effective user session key.
     *
     * @param tc
     *            context to use
     * @param chlng
     *            The server challenge.
     * @param dest
     *            The destination array in which the user session key will be
     *            placed.
     * @param offset
     *            The offset in the destination array at which the
     *            session key will start.
     * @throws SmbException
     */
    public void getUserSessionKey ( CIFSContext tc, byte[] chlng, byte[] dest, int offset ) throws SmbException {
        try {
            MessageDigest md4 = Crypto.getMD4();
            byte[] ntHash = getNTHash();
            switch ( tc.getConfig().getLanManCompatibility() ) {
            case 0:
            case 1:
            case 2:
                md4.update(ntHash);
                md4.digest(dest, offset, 16);
                break;
            case 3:
            case 4:
            case 5:
                synchronized ( this ) {
                    if ( this.clientChallenge == null ) {
                        this.clientChallenge = new byte[8];
                        tc.getConfig().getRandom().nextBytes(this.clientChallenge);
                    }
                }

                MessageDigest hmac = Crypto.getHMACT64(ntHash);
                hmac.update(Strings.getUNIBytes(this.username.toUpperCase()));
                hmac.update(Strings.getUNIBytes(this.domain.toUpperCase()));
                byte[] ntlmv2Hash = hmac.digest();
                hmac = Crypto.getHMACT64(ntlmv2Hash);
                hmac.update(chlng);
                hmac.update(this.clientChallenge);
                MessageDigest userKey = Crypto.getHMACT64(ntlmv2Hash);
                userKey.update(hmac.digest());
                userKey.digest(dest, offset, 16);
                break;
            default:
                md4.update(ntHash);
                md4.digest(dest, offset, 16);
                break;
            }
        }
        catch ( Exception e ) {
            throw new SmbException("", e);
        }
    }


    /**
     * @return
     */
    protected byte[] getNTHash () {
        MessageDigest md4 = Crypto.getMD4();
        md4.update(Strings.getUNIBytes(this.password));
        byte[] ntHash = md4.digest();
        return ntHash;
    }

    /**
     * Authentication strategy
     * 
     * 
     */
    public enum AuthenticationType {
        /**
         * Null/anonymous authentication
         * 
         * Login with no credentials
         */
        NULL,
        /**
         * Guest authentication
         * 
         * Allows login with invalid credentials (username and/or password)
         * Fallback to anonymous authentication is permitted
         */
        GUEST,
        /**
         * Regular user authentication
         */
        USER
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy