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

jcifs.smb.NtlmPasswordAuthentication Maven / Gradle / Ivy

There is a newer version: 2.1.10
Show newest version
/* jcifs smb client library in Java
 * Copyright (C) 2002  "Michael B. Allen" 
 *                  "Eric Glass" 
 *
 * 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.Serializable;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.Principal;
import java.util.Arrays;
import java.util.Objects;

import javax.security.auth.Subject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jcifs.CIFSContext;
import jcifs.Credentials;
import jcifs.RuntimeCIFSException;
import jcifs.util.Crypto;
import jcifs.util.Strings;


/**
 * This class stores and encrypts NTLM user credentials. The default
 * credentials are retrieved from the jcifs.smb.client.domain,
 * jcifs.smb.client.username, and jcifs.smb.client.password
 * properties.
 * 

* Read jCIFS Exceptions and * NtlmAuthenticator for related information. */ public class NtlmPasswordAuthentication implements Principal, CredentialsInternal, Serializable { /** * */ private static final long serialVersionUID = -2832037191318016836L; private static final Logger log = LoggerFactory.getLogger(NtlmPasswordAuthentication.class); private String domain; private String username; private String password; private byte[] ansiHash; private byte[] unicodeHash; private boolean hashesExternal = false; private byte[] clientChallenge = null; private boolean nullAuth; private CIFSContext context; /** * */ private NtlmPasswordAuthentication () {} @SuppressWarnings ( "unchecked" ) @Override public T unwrap ( Class type ) { if ( type.isAssignableFrom(this.getClass()) ) { return (T) this; } return null; } /** * Create an NtlmPasswordAuthentication object from a * domain, username, and password. Parameters that are null * will be substituted with jcifs.smb.client.domain, * jcifs.smb.client.username, jcifs.smb.client.password * property values. * * @param tc * context to use * @param domain * @param username * @param password */ public NtlmPasswordAuthentication ( CIFSContext tc, String domain, String username, String password ) { this.context = tc; int ci; if ( username != null ) { 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; this.username = username; this.password = password; setupDefaults(tc); } /** * Create an NtlmPasswordAuthentication object with raw password * hashes. This is used exclusively by the jcifs.http.NtlmSsp * class which is in turn used by NTLM HTTP authentication functionality. * * @param domain * @param username * @param challenge * @param ansiHash * @param unicodeHash */ public NtlmPasswordAuthentication ( String domain, String username, byte[] challenge, byte[] ansiHash, byte[] unicodeHash ) { if ( domain == null || username == null || ansiHash == null || unicodeHash == null ) { throw new IllegalArgumentException("External credentials cannot be null"); } this.domain = domain; this.username = username; this.password = null; this.ansiHash = ansiHash; this.unicodeHash = unicodeHash; this.hashesExternal = true; } /** * Construct anonymous credentials * * @param tc */ public NtlmPasswordAuthentication ( CIFSContext tc ) { this(tc, "", "", ""); } /** * Create an NtlmPasswordAuthentication object from the userinfo * component of an SMB URL like "domain;user:pass". This constructor * is used internally be jCIFS when parsing SMB URLs. * * @param tc * @param userInfo */ public NtlmPasswordAuthentication ( CIFSContext tc, String userInfo ) { this.domain = this.username = this.password = null; if ( userInfo != null ) { try { userInfo = unescape(userInfo); } catch ( UnsupportedEncodingException uee ) { throw new RuntimeCIFSException(uee); } int i, u, end; char c; end = userInfo.length(); for ( i = 0, u = 0; i < end; i++ ) { c = userInfo.charAt(i); if ( c == ';' ) { this.domain = userInfo.substring(0, i); u = i + 1; } else if ( c == ':' ) { this.password = userInfo.substring(i + 1); break; } } this.username = userInfo.substring(u, i); } setupDefaults(tc); } /** * @return the context */ protected CIFSContext getContext () { return this.context; } /** * {@inheritDoc} * * @see jcifs.smb.CredentialsInternal#getSubject() */ @Override public Subject getSubject () { return null; } /** * * {@inheritDoc} * * @see jcifs.smb.CredentialsInternal#createContext(jcifs.CIFSContext, java.lang.String, java.lang.String, byte[], * boolean) */ @Override public SSPContext createContext ( CIFSContext transportContext, String targetDomain, String host, byte[] initialToken, boolean doSigning ) throws SmbException { return new NtlmContext(transportContext, this, doSigning); } @Override public NtlmPasswordAuthentication clone () { NtlmPasswordAuthentication cloned = new NtlmPasswordAuthentication(); cloneInternal(cloned, this); return cloned; } /** * @param to * @param from */ protected static void cloneInternal ( NtlmPasswordAuthentication to, NtlmPasswordAuthentication from ) { to.domain = from.domain; to.username = from.username; to.password = from.password; to.nullAuth = from.nullAuth; if ( from.hashesExternal ) { to.hashesExternal = true; to.ansiHash = from.ansiHash != null ? Arrays.copyOf(from.ansiHash, from.ansiHash.length) : null; to.unicodeHash = from.unicodeHash != null ? Arrays.copyOf(from.unicodeHash, from.unicodeHash.length) : null; } } /** * @param tc */ private void setupDefaults ( CIFSContext tc ) { if ( this.domain == null ) this.domain = tc.getConfig().getDefaultDomain(); if ( this.username == null ) this.username = tc.getConfig().getDefaultUsername() != null ? tc.getConfig().getDefaultUsername() : "GUEST"; if ( this.password == null ) this.password = tc.getConfig().getDefaultPassword() != null ? tc.getConfig().getDefaultPassword() : ""; } /** * 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; } /** * 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 { if ( this.hashesExternal ) { return this.ansiHash; } 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 { if ( this.hashesExternal ) { return this.unicodeHash; } 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 ) { if ( this.hashesExternal ) return null; 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 { if ( this.hashesExternal ) return; try { MessageDigest md4 = Crypto.getMD4(); md4.update(Strings.getUNIBytes(this.password)); switch ( tc.getConfig().getLanManCompatibility() ) { case 0: case 1: case 2: md4.update(md4.digest()); md4.digest(dest, offset, 16); break; case 3: case 4: case 5: if ( this.clientChallenge == null ) { this.clientChallenge = new byte[8]; tc.getConfig().getRandom().nextBytes(this.clientChallenge); } MessageDigest hmac = Crypto.getHMACT64(md4.digest()); 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(md4.digest()); md4.digest(dest, offset, 16); break; } } catch ( Exception e ) { throw new SmbException("", e); } } /** * Compares two NtlmPasswordAuthentication objects for * equality. Two NtlmPasswordAuthentication objects are equal if * their caseless domain and username fields are equal and either both hashes are external and they are equal or * both internally supplied passwords are equal. If one NtlmPasswordAuthentication object has external * hashes (meaning negotiated via NTLM HTTP Authentication) and the other does not they will not be equal. This is * technically not correct however the server 8 byte challage would be required to compute and compare the password * hashes but that it not available with this method. */ @Override public boolean equals ( Object obj ) { if ( obj instanceof NtlmPasswordAuthentication ) { NtlmPasswordAuthentication ntlm = (NtlmPasswordAuthentication) obj; String domA = ntlm.getUserDomain() != null ? ntlm.getUserDomain().toUpperCase() : null; String domB = this.getUserDomain() != null ? this.getUserDomain().toUpperCase() : null; if ( Objects.equals(domA, domB) && ntlm.getUsername().equalsIgnoreCase(this.getUsername()) ) { if ( this.areHashesExternal() && ntlm.areHashesExternal() ) { return Arrays.equals(this.ansiHash, ntlm.ansiHash) && Arrays.equals(this.unicodeHash, ntlm.unicodeHash); /* * This still isn't quite right. If one npa object does not have external * hashes and the other does then they will not be considered equal even * though they may be. */ } else if ( !this.areHashesExternal() && this.getPassword().equals(ntlm.getPassword()) ) { return true; } } } 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 ( getUserDomain() == null || getUserDomain().isEmpty() ) && ( getUsername().isEmpty() || isGuest() ) && getPassword().isEmpty(); } @Override public boolean isGuest () { return "GUEST".equalsIgnoreCase(getUsername()); } 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 dependant * 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); } /** * @return whether the hashes are externally supplied */ public boolean areHashesExternal () { return this.hashesExternal; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy