Client: Added API to provide custom authentication

This commit is contained in:
Tal Aloni 2023-10-27 12:52:55 +03:00
parent a0b07521a1
commit 3a7e36b6d1
5 changed files with 172 additions and 81 deletions

View file

@ -0,0 +1,16 @@
/* Copyright (C) 2023-2023 Tal Aloni <tal.aloni.il@gmail.com>. 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
{
/// <returns>Credentials blob or null if security blob is invalid</returns>
byte[] InitializeSecurityContext(byte[] securityBlob);
byte[] GetSessionKey();
}
}

View file

@ -0,0 +1,135 @@
/* Copyright (C) 2017-2023 Tal Aloni <tal.aloni.il@gmail.com>. 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<byte[]>();
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;
}
}
}

View file

@ -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<byte[]>();
outputToken.MechanismTypeList.Add(GSSProvider.NTLMSSPIdentifier);
outputToken.MechanismToken = negotiateMessage.GetBytes();
return outputToken.GetBytes(true);
}
else
{
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,17 +162,8 @@ namespace SMBLibrary.Client
sessionKey = keyExchangeKey;
}
if (useGSSAPI)
{
SimpleProtectedNegotiationTokenResponse outputToken = new SimpleProtectedNegotiationTokenResponse();
outputToken.ResponseToken = authenticateMessage.GetBytes();
return outputToken.GetBytes();
}
else
{
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;
}
}
}

View file

@ -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();

View file

@ -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();