From 3a7e36b6d1afecf85a726c54bc8a18ff31ad2b70 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 Oct 2023 12:52:55 +0300 Subject: [PATCH] Client: Added API to provide custom authentication --- .../Authentication/IAuthenticationClient.cs | 16 +++ .../NTLMAuthenticationClient.cs | 135 ++++++++++++++++++ .../Helpers/NTLMAuthenticationHelper.cs | 81 +---------- SMBLibrary/Client/SMB1Client.cs | 7 +- SMBLibrary/Client/SMB2Client.cs | 14 +- 5 files changed, 172 insertions(+), 81 deletions(-) create mode 100644 SMBLibrary/Client/Authentication/IAuthenticationClient.cs create mode 100644 SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs diff --git a/SMBLibrary/Client/Authentication/IAuthenticationClient.cs b/SMBLibrary/Client/Authentication/IAuthenticationClient.cs new file mode 100644 index 0000000..fe9115c --- /dev/null +++ b/SMBLibrary/Client/Authentication/IAuthenticationClient.cs @@ -0,0 +1,16 @@ +/* Copyright (C) 2023-2023 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +namespace SMBLibrary.Client.Authentication +{ + public interface IAuthenticationClient + { + /// Credentials blob or null if security blob is invalid + byte[] InitializeSecurityContext(byte[] securityBlob); + + byte[] GetSessionKey(); + } +} diff --git a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs new file mode 100644 index 0000000..50c3739 --- /dev/null +++ b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs @@ -0,0 +1,135 @@ +/* Copyright (C) 2017-2023 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using SMBLibrary.Authentication.GSSAPI; +using System.Collections.Generic; +using Utilities; + +namespace SMBLibrary.Client.Authentication +{ + public class NTLMAuthenticationClient : IAuthenticationClient + { + private string m_domainName; + private string m_userName; + private string m_password; + private string m_spn; + private byte[] m_sessionKey; + private AuthenticationMethod m_authenticationMethod; + + private bool m_isNegotiationMessageAcquired = false; + + public NTLMAuthenticationClient(string domainName, string userName, string password, string spn, AuthenticationMethod authenticationMethod) + { + m_domainName = domainName; + m_userName = userName; + m_password = password; + m_spn = spn; + m_authenticationMethod = authenticationMethod; + } + + public byte[] InitializeSecurityContext(byte[] securityBlob) + { + if (!m_isNegotiationMessageAcquired) + { + m_isNegotiationMessageAcquired = true; + return GetNegotiateMessage(securityBlob); + } + else + { + return GetAuthenticateMessage(securityBlob); + } + } + + protected virtual byte[] GetNegotiateMessage(byte[] securityBlob) + { + bool useGSSAPI = false; + if (securityBlob.Length > 0) + { + SimpleProtectedNegotiationTokenInit spnegoToken = null; + try + { + spnegoToken = SimpleProtectedNegotiationToken.ReadToken(securityBlob, 0, true) as SimpleProtectedNegotiationTokenInit; + } + catch + { + } + + if (spnegoToken == null || !ContainsMechanism(spnegoToken, GSSProvider.NTLMSSPIdentifier)) + { + return null; + } + useGSSAPI = true; + } + + byte[] negotiateMessageBytes = NTLMAuthenticationHelper.GetNegotiateMessage(m_domainName, m_userName, m_password, m_authenticationMethod); + if (useGSSAPI) + { + SimpleProtectedNegotiationTokenInit outputToken = new SimpleProtectedNegotiationTokenInit(); + outputToken.MechanismTypeList = new List(); + outputToken.MechanismTypeList.Add(GSSProvider.NTLMSSPIdentifier); + outputToken.MechanismToken = negotiateMessageBytes; + return outputToken.GetBytes(true); + } + else + { + return negotiateMessageBytes; + } + } + + protected virtual byte[] GetAuthenticateMessage(byte[] securityBlob) + { + bool useGSSAPI = false; + SimpleProtectedNegotiationTokenResponse spnegoToken = null; + try + { + spnegoToken = SimpleProtectedNegotiationToken.ReadToken(securityBlob, 0, false) as SimpleProtectedNegotiationTokenResponse; + } + catch + { + } + + byte[] challengeMessageBytes; + if (spnegoToken != null) + { + challengeMessageBytes = spnegoToken.ResponseToken; + useGSSAPI = true; + } + else + { + challengeMessageBytes = securityBlob; + } + + byte[] authenticationMessageBytes = NTLMAuthenticationHelper.GetAuthenticateMessage(challengeMessageBytes, m_domainName, m_userName, m_password, m_spn, m_authenticationMethod, out m_sessionKey); + if (useGSSAPI && authenticationMessageBytes != null) + { + SimpleProtectedNegotiationTokenResponse outputToken = new SimpleProtectedNegotiationTokenResponse(); + outputToken.ResponseToken = authenticationMessageBytes; + return outputToken.GetBytes(); + } + else + { + return authenticationMessageBytes; + } + } + + public virtual byte[] GetSessionKey() + { + return m_sessionKey; + } + + private static bool ContainsMechanism(SimpleProtectedNegotiationTokenInit token, byte[] mechanismIdentifier) + { + for (int index = 0; index < token.MechanismTypeList.Count; index++) + { + if (ByteUtils.AreByteArraysEqual(token.MechanismTypeList[index], mechanismIdentifier)) + { + return true; + } + } + return false; + } + } +} diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index 184194b..47dc960 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Security.Cryptography; -using SMBLibrary.Authentication.GSSAPI; using SMBLibrary.Authentication.NTLM; using Utilities; @@ -15,27 +14,8 @@ namespace SMBLibrary.Client { public class NTLMAuthenticationHelper { - public static byte[] GetNegotiateMessage(byte[] securityBlob, string domainName, string userName, string password, AuthenticationMethod authenticationMethod) + public static byte[] GetNegotiateMessage(string domainName, string userName, string password, AuthenticationMethod authenticationMethod) { - bool useGSSAPI = false; - if (securityBlob.Length > 0) - { - SimpleProtectedNegotiationTokenInit inputToken = null; - try - { - inputToken = SimpleProtectedNegotiationToken.ReadToken(securityBlob, 0, true) as SimpleProtectedNegotiationTokenInit; - } - catch - { - } - - if (inputToken == null || !ContainsMechanism(inputToken, GSSProvider.NTLMSSPIdentifier)) - { - return null; - } - useGSSAPI = true; - } - NegotiateMessage negotiateMessage = new NegotiateMessage(); negotiateMessage.NegotiateFlags = NegotiateFlags.UnicodeEncoding | NegotiateFlags.OEMEncoding | @@ -65,44 +45,14 @@ namespace SMBLibrary.Client negotiateMessage.Version = NTLMVersion.Server2003; negotiateMessage.DomainName = domainName; negotiateMessage.Workstation = Environment.MachineName; - if (useGSSAPI) - { - SimpleProtectedNegotiationTokenInit outputToken = new SimpleProtectedNegotiationTokenInit(); - outputToken.MechanismTypeList = new List(); - outputToken.MechanismTypeList.Add(GSSProvider.NTLMSSPIdentifier); - outputToken.MechanismToken = negotiateMessage.GetBytes(); - return outputToken.GetBytes(true); - } - else - { - return negotiateMessage.GetBytes(); - } + return negotiateMessage.GetBytes(); } - public static byte[] GetAuthenticateMessage(byte[] securityBlob, string domainName, string userName, string password, string spn, AuthenticationMethod authenticationMethod, out byte[] sessionKey) + public static byte[] GetAuthenticateMessage(byte[] challengeMessageBytes, string domainName, string userName, string password, string spn, AuthenticationMethod authenticationMethod, out byte[] sessionKey) { sessionKey = null; - bool useGSSAPI = false; - SimpleProtectedNegotiationTokenResponse inputToken = null; - try - { - inputToken = SimpleProtectedNegotiationToken.ReadToken(securityBlob, 0, false) as SimpleProtectedNegotiationTokenResponse; - } - catch - { - } - - ChallengeMessage challengeMessage; - if (inputToken != null) - { - challengeMessage = GetChallengeMessage(inputToken.ResponseToken); - useGSSAPI = true; - } - else - { - challengeMessage = GetChallengeMessage(securityBlob); - } + ChallengeMessage challengeMessage = GetChallengeMessage(challengeMessageBytes); if (challengeMessage == null) { return null; @@ -212,16 +162,7 @@ namespace SMBLibrary.Client sessionKey = keyExchangeKey; } - if (useGSSAPI) - { - SimpleProtectedNegotiationTokenResponse outputToken = new SimpleProtectedNegotiationTokenResponse(); - outputToken.ResponseToken = authenticateMessage.GetBytes(); - return outputToken.GetBytes(); - } - else - { - return authenticateMessage.GetBytes(); - } + return authenticateMessage.GetBytes(); } private static ChallengeMessage GetChallengeMessage(byte[] messageBytes) @@ -243,17 +184,5 @@ namespace SMBLibrary.Client } return null; } - - private static bool ContainsMechanism(SimpleProtectedNegotiationTokenInit token, byte[] mechanismIdentifier) - { - for (int index = 0; index < token.MechanismTypeList.Count; index++) - { - if (ByteUtils.AreByteArraysEqual(token.MechanismTypeList[index], GSSProvider.NTLMSSPIdentifier)) - { - return true; - } - } - return false; - } } } diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 175f68d..9ebf50d 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -11,6 +11,7 @@ using System.Net; using System.Net.Sockets; using System.Threading; using SMBLibrary.Authentication.NTLM; +using SMBLibrary.Client.Authentication; using SMBLibrary.NetBios; using SMBLibrary.Services; using SMBLibrary.SMB1; @@ -289,7 +290,8 @@ namespace SMBLibrary.Client } else // m_securityBlob != null { - byte[] negotiateMessage = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, userName, password, authenticationMethod); + NTLMAuthenticationClient authenticationClient = new NTLMAuthenticationClient(domainName, userName, password, null, authenticationMethod); + byte[] negotiateMessage = authenticationClient.InitializeSecurityContext(m_securityBlob); if (negotiateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; @@ -308,11 +310,12 @@ namespace SMBLibrary.Client if (reply.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED && reply.Commands[0] is SessionSetupAndXResponseExtended) { SessionSetupAndXResponseExtended response = (SessionSetupAndXResponseExtended)reply.Commands[0]; - byte[] authenticateMessage = NTLMAuthenticationHelper.GetAuthenticateMessage(response.SecurityBlob, domainName, userName, password, null, authenticationMethod, out m_sessionKey); + byte[] authenticateMessage = authenticationClient.InitializeSecurityContext(response.SecurityBlob); if (authenticateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; } + m_sessionKey = authenticationClient.GetSessionKey(); m_userID = reply.Header.UID; request = new SessionSetupAndXRequestExtended(); diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 2e3eeb7..4ef93a8 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -10,6 +10,7 @@ using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Threading; +using SMBLibrary.Client.Authentication; using SMBLibrary.NetBios; using SMBLibrary.SMB2; using Utilities; @@ -208,13 +209,20 @@ namespace SMBLibrary.Client } public NTStatus Login(string domainName, string userName, string password, AuthenticationMethod authenticationMethod) + { + string spn = string.Format("cifs/{0}", m_serverName); + NTLMAuthenticationClient authenticationClient = new NTLMAuthenticationClient(domainName, userName, password, spn, authenticationMethod); + return Login(authenticationClient); + } + + public NTStatus Login(IAuthenticationClient authenticationClient) { if (!m_isConnected) { throw new InvalidOperationException("A connection must be successfully established before attempting login"); } - byte[] negotiateMessage = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, userName, password, authenticationMethod); + byte[] negotiateMessage = authenticationClient.InitializeSecurityContext(m_securityBlob); if (negotiateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; @@ -229,12 +237,12 @@ namespace SMBLibrary.Client { if (response.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED && response is SessionSetupResponse) { - string spn = string.Format("cifs/{0}", m_serverName); - byte[] authenticateMessage = NTLMAuthenticationHelper.GetAuthenticateMessage(((SessionSetupResponse)response).SecurityBuffer, domainName, userName, password, spn, authenticationMethod, out m_sessionKey); + byte[] authenticateMessage = authenticationClient.InitializeSecurityContext(((SessionSetupResponse)response).SecurityBuffer); if (authenticateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; } + m_sessionKey = authenticationClient.GetSessionKey(); m_sessionID = response.Header.SessionID; request = new SessionSetupRequest();