Commit d10bb71a authored by justcoding121's avatar justcoding121 Committed by justcoding121

#139 Fix Win Auth

parent d431a3ef
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using Titanium.Web.Proxy.Network.WinAuth; using Titanium.Web.Proxy.Network.WinAuth;
namespace Titanium.Web.Proxy.UnitTests namespace Titanium.Web.Proxy.UnitTests
...@@ -9,7 +10,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -9,7 +10,7 @@ namespace Titanium.Web.Proxy.UnitTests
[TestMethod] [TestMethod]
public void Test_Acquire_Client_Token() public void Test_Acquire_Client_Token()
{ {
var token = WinAuthHandler.GetInitialAuthToken("mylocalserver.com"); var token = WinAuthHandler.GetInitialAuthToken("mylocalserver.com", "NTLM", Guid.NewGuid());
Assert.IsTrue(token.Length > 1); Assert.IsTrue(token.Length > 1);
} }
} }
......
...@@ -44,7 +44,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -44,7 +44,7 @@ namespace Titanium.Web.Proxy.EventArguments
public Guid Id => WebSession.RequestId; public Guid Id => WebSession.RequestId;
/// <summary> /// <summary>
/// Should we send a rerequest /// Should we send the request again
/// </summary> /// </summary>
public bool ReRequest { get; set; } public bool ReRequest { get; set; }
...@@ -136,9 +136,21 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -136,9 +136,21 @@ namespace Titanium.Web.Proxy.EventArguments
} }
/// <summary> /// <summary>
/// Read response body as byte[] for current response /// reinit response object
/// </summary> /// </summary>
private async Task ReadResponseBody() internal async Task ClearResponse()
{
//siphon out the body
await ReadResponseBody();
WebSession.Response.Dispose();
WebSession.Response = new Response();
}
/// <summary>
/// Read response body as byte[] for current response
/// </summary>
private async Task ReadResponseBody()
{ {
//If not already read (not cached yet) //If not already read (not cached yet)
if (WebSession.Response.ResponseBody == null) if (WebSession.Response.ResponseBody == null)
......
...@@ -3,13 +3,30 @@ ...@@ -3,13 +3,30 @@
namespace Titanium.Web.Proxy.Network.WinAuth.Security namespace Titanium.Web.Proxy.Network.WinAuth.Security
{ {
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Principal; using System.Security.Principal;
using static Common; using static Common;
internal class EndPoint internal class EndPoint
{ {
internal static byte[] AcquireInitialSecurityToken(string hostname) /// <summary>
/// Keep track of auth states for reuse in final challenge response
/// </summary>
private static IDictionary<Guid, State> authStates
= new ConcurrentDictionary<Guid, State>();
/// <summary>
/// Acquire the intial client token to send
/// </summary>
/// <param name="hostname"></param>
/// <param name="authScheme"></param>
/// <param name="requestId"></param>
/// <returns></returns>
internal static byte[] AcquireInitialSecurityToken(string hostname,
string authScheme, Guid requestId)
{ {
byte[] token = null; byte[] token = null;
...@@ -28,8 +45,8 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -28,8 +45,8 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
result = AcquireCredentialsHandle( result = AcquireCredentialsHandle(
WindowsIdentity.GetCurrent().Name, WindowsIdentity.GetCurrent().Name,
"NTLM", authScheme,
Common.SecurityCredentialsOutbound, SecurityCredentialsOutbound,
IntPtr.Zero, IntPtr.Zero,
IntPtr.Zero, IntPtr.Zero,
0, 0,
...@@ -64,6 +81,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -64,6 +81,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
} }
token = clientToken.GetBytes(); token = clientToken.GetBytes();
authStates.Add(requestId, state);
} }
finally finally
{ {
...@@ -79,8 +97,10 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -79,8 +97,10 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
/// </summary> /// </summary>
/// <param name="hostname"></param> /// <param name="hostname"></param>
/// <param name="serverChallenge"></param> /// <param name="serverChallenge"></param>
/// <param name="requestId"></param>
/// <returns></returns> /// <returns></returns>
internal static byte[] AcquireFinalSecurityToken(string hostname, byte[] serverChallenge) internal static byte[] AcquireFinalSecurityToken(string hostname,
byte[] serverChallenge, Guid requestId)
{ {
byte[] token = null; byte[] token = null;
...@@ -95,27 +115,10 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -95,27 +115,10 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
{ {
int result; int result;
var state = new State(); var state = authStates[requestId];
result = AcquireCredentialsHandle(
WindowsIdentity.GetCurrent().Name,
"NTLM",
SecurityCredentialsOutbound,
IntPtr.Zero,
IntPtr.Zero,
0,
IntPtr.Zero,
ref state.Credentials,
ref NewLifeTime);
if (result != SuccessfulResult)
{
// Credentials acquire operation failed.
return null;
}
result = InitializeSecurityContext(ref state.Credentials, result = InitializeSecurityContext(ref state.Credentials,
IntPtr.Zero, ref state.Context,
hostname, hostname,
StandardContextAttributes, StandardContextAttributes,
0, 0,
...@@ -134,6 +137,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -134,6 +137,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
return null; return null;
} }
authStates.Remove(requestId);
token = clientToken.GetBytes(); token = clientToken.GetBytes();
} }
finally finally
...@@ -156,9 +160,23 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -156,9 +160,23 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
ref SecurityBufferDesciption pInput, //PSecBufferDesc SecBufferDesc ref SecurityBufferDesciption pInput, //PSecBufferDesc SecBufferDesc
int Reserved2, int Reserved2,
out SecurityHandle phNewContext, //PCtxtHandle out SecurityHandle phNewContext, //PCtxtHandle
out Common.SecurityBufferDesciption pOutput, //PSecBufferDesc SecBufferDesc out SecurityBufferDesciption pOutput, //PSecBufferDesc SecBufferDesc
out uint pfContextAttr, //managed ulong == 64 bits!!! out uint pfContextAttr, //managed ulong == 64 bits!!!
out Common.SecurityInteger ptsExpiry); //PTimeStamp out SecurityInteger ptsExpiry); //PTimeStamp
[DllImport("secur32", CharSet = CharSet.Auto, SetLastError = true)]
static extern int InitializeSecurityContext(ref SecurityHandle phCredential,//PCredHandle
ref SecurityHandle phContext, //PCtxtHandle
string pszTargetName,
int fContextReq,
int Reserved1,
int TargetDataRep,
ref SecurityBufferDesciption SecBufferDesc, //PSecBufferDesc SecBufferDesc
int Reserved2,
out SecurityHandle phNewContext, //PCtxtHandle
out SecurityBufferDesciption pOutput, //PSecBufferDesc SecBufferDesc
out uint pfContextAttr, //managed ulong == 64 bits!!!
out SecurityInteger ptsExpiry); //PTimeStamp
[DllImport("secur32.dll", CharSet = CharSet.Auto, SetLastError = false)] [DllImport("secur32.dll", CharSet = CharSet.Auto, SetLastError = false)]
private static extern int AcquireCredentialsHandle( private static extern int AcquireCredentialsHandle(
......
...@@ -15,10 +15,13 @@ namespace Titanium.Web.Proxy.Network.WinAuth ...@@ -15,10 +15,13 @@ namespace Titanium.Web.Proxy.Network.WinAuth
/// using credentials of user running the proxy server process /// using credentials of user running the proxy server process
/// </summary> /// </summary>
/// <param name="serverHostname"></param> /// <param name="serverHostname"></param>
/// <param name="authScheme"></param>
/// <param name="requestId"></param>
/// <returns></returns> /// <returns></returns>
public static string GetInitialAuthToken(string serverHostname) public static string GetInitialAuthToken(string serverHostname,
string authScheme, Guid requestId)
{ {
var tokenBytes = EndPoint.AcquireInitialSecurityToken(serverHostname); var tokenBytes = EndPoint.AcquireInitialSecurityToken(serverHostname, authScheme, requestId);
return string.Concat(" ", Convert.ToBase64String(tokenBytes)); return string.Concat(" ", Convert.ToBase64String(tokenBytes));
} }
...@@ -28,10 +31,14 @@ namespace Titanium.Web.Proxy.Network.WinAuth ...@@ -28,10 +31,14 @@ namespace Titanium.Web.Proxy.Network.WinAuth
/// </summary> /// </summary>
/// <param name="serverHostname"></param> /// <param name="serverHostname"></param>
/// <param name="serverToken"></param> /// <param name="serverToken"></param>
/// <param name="requestId"></param>
/// <returns></returns> /// <returns></returns>
public static string GetFinalAuthToken(string serverHostname, string serverToken) public static string GetFinalAuthToken(string serverHostname,
string serverToken, Guid requestId)
{ {
var tokenBytes = EndPoint.AcquireFinalSecurityToken(serverHostname, Convert.FromBase64String(serverToken)); var tokenBytes = EndPoint.AcquireFinalSecurityToken(serverHostname,
Convert.FromBase64String(serverToken), requestId);
return string.Concat(" ", Convert.ToBase64String(tokenBytes)); return string.Concat(" ", Convert.ToBase64String(tokenBytes));
} }
......
...@@ -196,6 +196,15 @@ namespace Titanium.Web.Proxy ...@@ -196,6 +196,15 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public bool ForwardToUpstreamGateway { get; set; } public bool ForwardToUpstreamGateway { get; set; }
/// <summary>
/// Enable disable Windows Authentication (NTLM/Kerberos)
/// Note: NTLM/Kerberos will always send local credentials of current user
/// who is running the proxy process. This is because a man
/// in middle attack is not currently supported
/// (which would require windows delegation enabled for this server process)
/// </summary>
public bool EnableWinAuth { get; set; } = true;
/// <summary> /// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary> /// </summary>
......
...@@ -36,6 +36,18 @@ namespace Titanium.Web.Proxy ...@@ -36,6 +36,18 @@ namespace Titanium.Web.Proxy
args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream; args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream;
} }
//check for windows authentication
if(EnableWinAuth &&
args.WebSession.Response.ResponseStatusCode == "401")
{
var disposed = await Handle401UnAuthorized(args);
if(disposed)
{
return true;
}
}
args.ReRequest = false; args.ReRequest = false;
//If user requested call back then do it //If user requested call back then do it
...@@ -44,16 +56,13 @@ namespace Titanium.Web.Proxy ...@@ -44,16 +56,13 @@ namespace Titanium.Web.Proxy
await BeforeResponse.InvokeParallelAsync(this, args); await BeforeResponse.InvokeParallelAsync(this, args);
} }
//if user requested to send request again
//likely after making modifications from User Response Handler
if (args.ReRequest) if (args.ReRequest)
{ {
if (args.WebSession.ServerConnection != null) //clear current response
{ await args.ClearResponse();
args.WebSession.ServerConnection.Dispose(); var disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false);
Interlocked.Decrement(ref serverConnectionCount);
}
var connection = await GetServerConnection(args);
var disposed = await HandleHttpSessionRequestInternal(null, args, true);
return disposed; return disposed;
} }
......
...@@ -127,6 +127,7 @@ ...@@ -127,6 +127,7 @@
<Compile Include="Shared\ProxyConstants.cs" /> <Compile Include="Shared\ProxyConstants.cs" />
<Compile Include="Network\Tcp\TcpRow.cs" /> <Compile Include="Network\Tcp\TcpRow.cs" />
<Compile Include="Network\Tcp\TcpTable.cs" /> <Compile Include="Network\Tcp\TcpTable.cs" />
<Compile Include="WinAuthHandler.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<COMReference Include="CERTENROLLLib"> <COMReference Include="CERTENROLLLib">
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.WinAuth;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
//possible header names
private static List<string> authHeaderNames
= new List<string>() {
"WWW-Authenticate",
//IIS 6.0 messed up names below
"WWWAuthenticate",
"NTLMAuthorization",
"NegotiateAuthorization",
"KerberosAuthorization"
};
private static List<string> authSchemes
= new List<string>() {
"NTLM",
"Negotiate",
"Kerberos"
};
/// <summary>
/// Handle windows NTLM authentication
/// Can expand this for Kerberos in future
/// Note: NTLM/Kerberos cannot do a man in middle operation
/// we do for HTTPS requests.
/// As such we will be sending local credentials of current
/// User to server to authenticate requests.
/// To disable this set ProxyServer.EnableWinAuth to false
/// </summary>
internal async Task<bool> Handle401UnAuthorized(SessionEventArgs args)
{
string headerName = null;
HttpHeader authHeader = null;
//check in non-unique headers first
var header = args.WebSession.Response
.NonUniqueResponseHeaders
.FirstOrDefault(x =>
authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
if (!header.Equals(new KeyValuePair<string, List<HttpHeader>>()))
{
headerName = header.Key;
}
if (headerName != null)
{
authHeader = args.WebSession.Response
.NonUniqueResponseHeaders[headerName]
.Where(x => authSchemes.Any(y => x.Value.ContainsIgnoreCase(y)))
.FirstOrDefault();
}
//check in unique headers
if (authHeader == null)
{
//check in non-unique headers first
var uHeader = args.WebSession.Response
.ResponseHeaders
.FirstOrDefault(x =>
authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase)));
if (!uHeader.Equals(new KeyValuePair<string, HttpHeader>()))
{
headerName = uHeader.Key;
}
if (headerName != null)
{
authHeader = authSchemes.Any(x => args.WebSession.Response
.ResponseHeaders[headerName].Value.ContainsIgnoreCase(x)) ?
args.WebSession.Response.ResponseHeaders[headerName] : null;
}
}
if (authHeader != null)
{
var scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase));
//initial value will match exactly any of the schemes
if (scheme != null)
{
var clientToken = WinAuthHandler.GetInitialAuthToken(args.WebSession.Request.Host, scheme, args.Id);
args.WebSession.Request.RequestHeaders.Add("Authorization", new HttpHeader("Authorization", string.Concat(scheme, clientToken)));
}
//challenge value will start with any of the scheme selected
else
{
scheme = authSchemes.FirstOrDefault(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase));
var serverToken = authHeader.Value.Substring(scheme.Length + 1);
var clientToken = WinAuthHandler.GetFinalAuthToken(args.WebSession.Request.Host, serverToken, args.Id);
args.WebSession.Request.RequestHeaders["Authorization"]
= new HttpHeader("Authorization", string.Concat(scheme, clientToken));
}
//clear current response
await args.ClearResponse();
var disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false);
return disposed;
}
return false;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment