// This code is derived from jcifs smb client library // Ported by J. Arturo // // 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 using System; using SharpCifs.Util; using SharpCifs.Util.Sharpen; namespace SharpCifs.Smb { /// This class stores and encrypts NTLM user credentials. /// /// 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 sealed class NtlmPasswordAuthentication : Principal { private static readonly int LmCompatibility = Config.GetInt("jcifs.smb.lmCompatibility" , 3); private static readonly Random Random = new Random(); private static LogStream _log = LogStream.GetInstance(); private static readonly byte[] S8 = { unchecked(unchecked(0x4b)), unchecked(unchecked(0x47)), unchecked(unchecked(0x53)), unchecked(unchecked(0x21)), unchecked(unchecked(0x40)), unchecked(unchecked(0x23)), unchecked(unchecked(0x24)), unchecked(unchecked(0x25)) }; // KGS!@#$% private static void E(byte[] key, byte[] data, byte[] e) { byte[] key7 = new byte[7]; byte[] e8 = new byte[8]; for (int i = 0; i < key.Length / 7; i++) { Array.Copy(key, i * 7, key7, 0, 7); DES des = new DES(key7); des.Encrypt(data, e8); Array.Copy(e8, 0, e, i * 8, 8); } } internal static string DefaultDomain; internal static string DefaultUsername; internal static string DefaultPassword; internal static readonly string Blank = string.Empty; public static readonly NtlmPasswordAuthentication Anonymous = new NtlmPasswordAuthentication (string.Empty, string.Empty, string.Empty); internal static void InitDefaults() { if (DefaultDomain != null) { return; } DefaultDomain = Config.GetProperty("jcifs.smb.client.domain", "?"); DefaultUsername = Config.GetProperty("jcifs.smb.client.username", "GUEST"); DefaultPassword = Config.GetProperty("jcifs.smb.client.password", Blank); } ///

Generate the ANSI DES hash for the password associated with these credentials. /// /// Generate the ANSI DES hash for the password associated with these credentials. /// public static byte[] GetPreNtlmResponse(string password, byte[] challenge) { byte[] p14 = new byte[14]; byte[] p21 = new byte[21]; byte[] p24 = new byte[24]; byte[] passwordBytes; try { passwordBytes = Runtime.GetBytesForString(password.ToUpper(), SmbConstants.OemEncoding); } catch (UnsupportedEncodingException uee) { throw new RuntimeException("Try setting jcifs.encoding=US-ASCII", uee); } int passwordLength = passwordBytes.Length; // Only encrypt the first 14 bytes of the password for Pre 0.12 NT LM if (passwordLength > 14) { passwordLength = 14; } Array.Copy(passwordBytes, 0, p14, 0, passwordLength); E(p14, S8, p21); E(p21, challenge, p24); return p24; } /// Generate the Unicode MD4 hash for the password associated with these credentials. /// /// Generate the Unicode MD4 hash for the password associated with these credentials. /// public static byte[] GetNtlmResponse(string password, byte[] challenge) { byte[] uni = null; byte[] p21 = new byte[21]; byte[] p24 = new byte[24]; try { uni = Runtime.GetBytesForString(password, SmbConstants.UniEncoding); } catch (UnsupportedEncodingException uee) { if (_log.Level > 0) { Runtime.PrintStackTrace(uee, _log); } } Md4 md4 = new Md4(); md4.Update(uni); try { md4.Digest(p21, 0, 16); } catch (Exception ex) { if (_log.Level > 0) { Runtime.PrintStackTrace(ex, _log); } } E(p21, challenge, p24); return p24; } /// Creates the LMv2 response for the supplied information. /// Creates the LMv2 response for the supplied information. /// The domain in which the username exists. /// The username. /// The user's password. /// The server challenge. /// The client challenge (nonce). public static byte[] GetLMv2Response(string domain, string user, string password, byte[] challenge, byte[] clientChallenge) { try { byte[] hash = new byte[16]; byte[] response = new byte[24]; // The next 2-1/2 lines of this should be placed with nTOWFv1 in place of password Md4 md4 = new Md4(); md4.Update(Runtime.GetBytesForString(password, SmbConstants.UniEncoding) ); Hmact64 hmac = new Hmact64(md4.Digest()); hmac.Update(Runtime.GetBytesForString(user.ToUpper(), SmbConstants.UniEncoding )); hmac.Update(Runtime.GetBytesForString(domain.ToUpper(), SmbConstants.UniEncoding )); hmac = new Hmact64(hmac.Digest()); hmac.Update(challenge); hmac.Update(clientChallenge); hmac.Digest(response, 0, 16); Array.Copy(clientChallenge, 0, response, 16, 8); return response; } catch (Exception ex) { if (_log.Level > 0) { Runtime.PrintStackTrace(ex, _log); } return null; } } public static byte[] GetNtlm2Response(byte[] nTowFv1, byte[] serverChallenge, byte [] clientChallenge) { byte[] sessionHash = new byte[8]; try { MessageDigest md5; md5 = MessageDigest.GetInstance("MD5"); md5.Update(serverChallenge); md5.Update(clientChallenge, 0, 8); Array.Copy(md5.Digest(), 0, sessionHash, 0, 8); } catch (Exception gse) { if (_log.Level > 0) { Runtime.PrintStackTrace(gse, _log); } throw new RuntimeException("MD5", gse); } byte[] key = new byte[21]; Array.Copy(nTowFv1, 0, key, 0, 16); byte[] ntResponse = new byte[24]; E(key, sessionHash, ntResponse); return ntResponse; } public static byte[] NtowFv1(string password) { if (password == null) { throw new RuntimeException("Password parameter is required"); } try { Md4 md4 = new Md4(); md4.Update(Runtime.GetBytesForString(password, SmbConstants.UniEncoding) ); return md4.Digest(); } catch (UnsupportedEncodingException uee) { throw new RuntimeException(uee.Message); } } public static byte[] NtowFv2(string domain, string username, string password) { try { Md4 md4 = new Md4(); md4.Update(Runtime.GetBytesForString(password, SmbConstants.UniEncoding) ); Hmact64 hmac = new Hmact64(md4.Digest()); hmac.Update(Runtime.GetBytesForString(username.ToUpper(), SmbConstants.UniEncoding )); hmac.Update(Runtime.GetBytesForString(domain, SmbConstants.UniEncoding)); return hmac.Digest(); } catch (UnsupportedEncodingException uee) { throw new RuntimeException(uee.Message); } } internal static byte[] ComputeResponse(byte[] responseKey, byte[] serverChallenge , byte[] clientData, int offset, int length) { Hmact64 hmac = new Hmact64(responseKey); hmac.Update(serverChallenge); hmac.Update(clientData, offset, length); byte[] mac = hmac.Digest(); byte[] ret = new byte[mac.Length + clientData.Length]; Array.Copy(mac, 0, ret, 0, mac.Length); Array.Copy(clientData, 0, ret, mac.Length, clientData.Length); return ret; } public static byte[] GetLMv2Response(byte[] responseKeyLm, byte[] serverChallenge , byte[] clientChallenge) { return ComputeResponse(responseKeyLm, serverChallenge , clientChallenge, 0, clientChallenge.Length); } public static byte[] GetNtlMv2Response(byte[] responseKeyNt, byte[] serverChallenge , byte[] clientChallenge, long nanos1601, byte[] targetInfo) { int targetInfoLength = targetInfo != null ? targetInfo.Length : 0; byte[] temp = new byte[28 + targetInfoLength + 4]; Encdec.Enc_uint32le(unchecked(0x00000101), temp, 0); // Header Encdec.Enc_uint32le(unchecked(0x00000000), temp, 4); // Reserved Encdec.Enc_uint64le(nanos1601, temp, 8); Array.Copy(clientChallenge, 0, temp, 16, 8); Encdec.Enc_uint32le(unchecked(0x00000000), temp, 24); // Unknown if (targetInfo != null) { Array.Copy(targetInfo, 0, temp, 28, targetInfoLength); } Encdec.Enc_uint32le(unchecked(0x00000000), temp, 28 + targetInfoLength); // mystery bytes! return ComputeResponse(responseKeyNt, serverChallenge , temp, 0, temp.Length); } internal static readonly NtlmPasswordAuthentication Null = new NtlmPasswordAuthentication (string.Empty, string.Empty, string.Empty); internal static readonly NtlmPasswordAuthentication Guest = new NtlmPasswordAuthentication ("?", "GUEST", string.Empty); internal static readonly NtlmPasswordAuthentication Default = new NtlmPasswordAuthentication (null); internal string Domain; internal string Username; internal string Password; internal byte[] AnsiHash; internal byte[] UnicodeHash; internal bool HashesExternal; internal byte[] ClientChallenge; internal byte[] Challenge; /// /// Create an NtlmPasswordAuthentication object from the userinfo /// component of an SMB URL like "domain;user:pass". /// /// /// 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. /// public NtlmPasswordAuthentication(string userInfo) { Domain = Username = Password = null; if (userInfo != null) { try { userInfo = Unescape(userInfo); } catch (UnsupportedEncodingException) { } int i; int u; int end; char c; end = userInfo.Length; for (i = 0, u = 0; i < end; i++) { c = userInfo[i]; if (c == ';') { Domain = Runtime.Substring(userInfo, 0, i); u = i + 1; } else { if (c == ':') { Password = Runtime.Substring(userInfo, i + 1); break; } } } Username = Runtime.Substring(userInfo, u, i); } InitDefaults(); if (Domain == null) { Domain = DefaultDomain; } if (Username == null) { Username = DefaultUsername; } if (Password == null) { Password = DefaultPassword; } } /// /// Create an NtlmPasswordAuthentication object from a /// domain, username, and password. /// /// /// 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. /// public NtlmPasswordAuthentication(string domain, string username, string password ) { int ci; if (username != null) { ci = username.IndexOf('@'); if (ci > 0) { domain = Runtime.Substring(username, ci + 1); username = Runtime.Substring(username, 0, ci); } else { ci = username.IndexOf('\\'); if (ci > 0) { domain = Runtime.Substring(username, 0, ci); username = Runtime.Substring(username, ci + 1); } } } this.Domain = domain; this.Username = username; this.Password = password; InitDefaults(); if (domain == null) { this.Domain = DefaultDomain; } if (username == null) { this.Username = DefaultUsername; } if (password == null) { this.Password = DefaultPassword; } } /// /// Create an NtlmPasswordAuthentication object with raw password /// hashes. /// /// /// 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. /// public NtlmPasswordAuthentication(string domain, string username, byte[] challenge , byte[] ansiHash, byte[] unicodeHash) { if (domain == null || username == null || ansiHash == null || unicodeHash == null) { throw new ArgumentException("External credentials cannot be null"); } this.Domain = domain; this.Username = username; Password = null; this.Challenge = challenge; this.AnsiHash = ansiHash; this.UnicodeHash = unicodeHash; HashesExternal = true; } /// Returns the domain. /// Returns the domain. public string GetDomain() { return Domain; } /// Returns the username. /// Returns the username. public string GetUsername() { return 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. /// /// /// 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. /// public string GetPassword() { return Password; } /// /// Return the domain and username in the format: /// domain\\username. /// /// /// Return the domain and username in the format: /// domain\\username. This is equivalent to toString(). /// public new string GetName() { bool d = Domain.Length > 0 && Domain.Equals("?") == false; return d ? Domain + "\\" + Username : Username; } /// Computes the 24 byte ANSI password hash given the 8 byte server challenge. /// /// Computes the 24 byte ANSI password hash given the 8 byte server challenge. /// public byte[] GetAnsiHash(byte[] challenge) { if (HashesExternal) { return AnsiHash; } switch (LmCompatibility) { case 0: case 1: { return GetPreNtlmResponse(Password, challenge); } case 2: { return GetNtlmResponse(Password, challenge); } case 3: case 4: case 5: { if (ClientChallenge == null) { ClientChallenge = new byte[8]; Random.NextBytes(ClientChallenge); } return GetLMv2Response(Domain, Username, Password, challenge, ClientChallenge); } default: { return GetPreNtlmResponse(Password, challenge); } } } /// Computes the 24 byte Unicode password hash given the 8 byte server challenge. /// /// Computes the 24 byte Unicode password hash given the 8 byte server challenge. /// public byte[] GetUnicodeHash(byte[] challenge) { if (HashesExternal) { return UnicodeHash; } switch (LmCompatibility) { case 0: case 1: case 2: { return GetNtlmResponse(Password, challenge); } case 3: case 4: case 5: { return new byte[0]; } default: { return GetNtlmResponse(Password, challenge); } } } /// public byte[] GetSigningKey(byte[] challenge) { switch (LmCompatibility) { case 0: case 1: case 2: { byte[] signingKey = new byte[40]; GetUserSessionKey(challenge, signingKey, 0); Array.Copy(GetUnicodeHash(challenge), 0, signingKey, 16, 24); return signingKey; } case 3: case 4: case 5: { 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. /// Returns the effective user session key. /// The server challenge. /// /// A byte[] containing the effective user session key, /// used in SMB MAC signing and NTLMSSP signing and sealing. /// public byte[] GetUserSessionKey(byte[] challenge) { if (HashesExternal) { return null; } byte[] key = new byte[16]; try { GetUserSessionKey(challenge, key, 0); } catch (Exception ex) { if (_log.Level > 0) { Runtime.PrintStackTrace(ex, _log); } } return key; } /// Calculates the effective user session key. /// Calculates the effective user session key. /// The server challenge. /// /// The destination array in which the user session key will be /// placed. /// /// /// The offset in the destination array at which the /// session key will start. /// /// internal void GetUserSessionKey(byte[] challenge, byte[] dest, int offset) { if (HashesExternal) { return; } try { Md4 md4 = new Md4(); md4.Update(Runtime.GetBytesForString(Password, SmbConstants.UniEncoding) ); switch (LmCompatibility) { case 0: case 1: case 2: { md4.Update(md4.Digest()); md4.Digest(dest, offset, 16); break; } case 3: case 4: case 5: { if (ClientChallenge == null) { ClientChallenge = new byte[8]; Random.NextBytes(ClientChallenge); } Hmact64 hmac = new Hmact64(md4.Digest()); hmac.Update(Runtime.GetBytesForString(Username.ToUpper(), SmbConstants.UniEncoding )); hmac.Update(Runtime.GetBytesForString(Domain.ToUpper(), SmbConstants.UniEncoding )); byte[] ntlmv2Hash = hmac.Digest(); hmac = new Hmact64(ntlmv2Hash); hmac.Update(challenge); hmac.Update(ClientChallenge); Hmact64 userKey = new Hmact64(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(string.Empty, e); } } /// /// Compares two NtlmPasswordAuthentication objects for /// equality. /// /// /// 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. /// public override bool Equals(object obj) { if (obj is NtlmPasswordAuthentication) { NtlmPasswordAuthentication ntlm = (NtlmPasswordAuthentication )obj; if (ntlm.Domain.ToUpper().Equals(Domain.ToUpper()) && ntlm.Username.ToUpper().Equals (Username.ToUpper())) { if (HashesExternal && ntlm.HashesExternal) { return Arrays.Equals(AnsiHash, ntlm.AnsiHash) && Arrays.Equals(UnicodeHash, ntlm. UnicodeHash); } if (!HashesExternal && Password.Equals(ntlm.Password)) { return true; } } } return false; } /// Return the upcased username hash code. /// Return the upcased username hash code. public override int GetHashCode() { return GetName().ToUpper().GetHashCode(); } /// /// Return the domain and username in the format: /// domain\\username. /// /// /// Return the domain and username in the format: /// domain\\username. This is equivalent to getName(). /// public override string ToString() { return GetName(); } /// /// internal static string Unescape(string str) { char ch; int i; int j; int state; int 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[i]; if (ch == '%') { state = 1; } else { @out[j++] = ch; } break; } case 1: { b[0] = unchecked((byte)(Convert.ToInt32(Runtime.Substring(str, i, i + 2), 16) & unchecked(0xFF))); @out[j++] = (Runtime.GetStringForBytes(b, 0, 1, "ASCII"))[0]; i++; state = 0; break; } } } return new string(@out, 0, j); } } }