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

Merge pull request #248 from justcoding121/dev-ntlm-kerberos

ntlm kerberos
parents b8f7b8c5 8ecbc88b
...@@ -18,6 +18,7 @@ Features ...@@ -18,6 +18,7 @@ Features
* Support mutual SSL authentication * Support mutual SSL authentication
* Fully asynchronous proxy * Fully asynchronous proxy
* Supports proxy authentication & automatic proxy detection * Supports proxy authentication & automatic proxy detection
* Kerberos/NTLM authentication over HTTP protocols for windows domain
Usage Usage
===== =====
...@@ -203,7 +204,6 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs ...@@ -203,7 +204,6 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs
``` ```
Future road map (Pull requests are welcome!) Future road map (Pull requests are welcome!)
============ ============
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
* Support Server Name Indication (SNI) for transparent endpoints * Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0 * Support HTTP 2.0
* Support SOCKS protocol * Support SOCKS protocol
......
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
<DefineConstants>TRACE</DefineConstants> <DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
...@@ -59,6 +60,7 @@ ...@@ -59,6 +60,7 @@
<Compile Include="CertificateManagerTests.cs" /> <Compile Include="CertificateManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyServerTests.cs" /> <Compile Include="ProxyServerTests.cs" />
<Compile Include="WinAuthTests.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj"> <ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
......
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using Titanium.Web.Proxy.Network.WinAuth;
namespace Titanium.Web.Proxy.UnitTests
{
[TestClass]
public class WinAuthTests
{
[TestMethod]
public void Test_Acquire_Client_Token()
{
var token = WinAuthHandler.GetInitialAuthToken("mylocalserver.com", "NTLM", Guid.NewGuid());
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; }
...@@ -135,6 +135,18 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -135,6 +135,18 @@ namespace Titanium.Web.Proxy.EventArguments
} }
} }
/// <summary>
/// reinit response object
/// </summary>
internal async Task ClearResponse()
{
//siphon out the body
await ReadResponseBody();
WebSession.Response.Dispose();
WebSession.Response = new Response();
}
/// <summary> /// <summary>
/// Read response body as byte[] for current response /// Read response body as byte[] for current response
/// </summary> /// </summary>
......
...@@ -7,13 +7,15 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -7,13 +7,15 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary> /// </summary>
internal class RunTime internal class RunTime
{ {
private static Lazy<bool> isMonoRuntime
= new Lazy<bool>(()=> Type.GetType("Mono.Runtime") != null);
/// <summary> /// <summary>
/// Checks if current run time is Mono /// Checks if current run time is Mono
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
internal static bool IsRunningOnMono() internal static bool IsRunningOnMono()
{ {
return Type.GetType("Mono.Runtime") != null; return isMonoRuntime.Value;
} }
} }
} }
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
internal struct BufferWrapper
{
internal byte[] Buffer;
internal Common.SecurityBufferType BufferType;
internal BufferWrapper(byte[] buffer, Common.SecurityBufferType bufferType)
{
if (buffer == null || buffer.Length == 0)
{
throw new ArgumentException("buffer cannot be null or 0 length");
}
Buffer = buffer;
BufferType = bufferType;
}
};
}
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
using System.Runtime.InteropServices;
internal class Common
{
#region Private constants
private const int ISC_REQ_REPLAY_DETECT = 0x00000004;
private const int ISC_REQ_SEQUENCE_DETECT = 0x00000008;
private const int ISC_REQ_CONFIDENTIALITY = 0x00000010;
private const int ISC_REQ_CONNECTION = 0x00000800;
#endregion
internal static uint NewContextAttributes = 0;
internal static Common.SecurityInteger NewLifeTime = new SecurityInteger(0);
#region internal constants
internal const int StandardContextAttributes = ISC_REQ_CONFIDENTIALITY | ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT | ISC_REQ_CONNECTION;
internal const int SecurityNativeDataRepresentation = 0x10;
internal const int MaximumTokenSize = 12288;
internal const int SecurityCredentialsOutbound = 2;
internal const int SuccessfulResult = 0;
internal const int IntermediateResult = 0x90312;
#endregion
#region internal enumerations
internal enum SecurityBufferType
{
SECBUFFER_VERSION = 0,
SECBUFFER_EMPTY = 0,
SECBUFFER_DATA = 1,
SECBUFFER_TOKEN = 2
}
[Flags]
internal enum NtlmFlags : int
{
// The client sets this flag to indicate that it supports Unicode strings.
NegotiateUnicode = 0x00000001,
// This is set to indicate that the client supports OEM strings.
NegotiateOem = 0x00000002,
// This requests that the server send the authentication target with the Type 2 reply.
RequestTarget = 0x00000004,
// Indicates that NTLM authentication is supported.
NegotiateNtlm = 0x00000200,
// When set, the client will send with the message the name of the domain in which the workstation has membership.
NegotiateDomainSupplied = 0x00001000,
// Indicates that the client is sending its workstation name with the message.
NegotiateWorkstationSupplied = 0x00002000,
// Indicates that communication between the client and server after authentication should carry a "dummy" signature.
NegotiateAlwaysSign = 0x00008000,
// Indicates that this client supports the NTLM2 signing and sealing scheme; if negotiated, this can also affect the response calculations.
NegotiateNtlm2Key = 0x00080000,
// Indicates that this client supports strong (128-bit) encryption.
Negotiate128 = 0x20000000,
// Indicates that this client supports medium (56-bit) encryption.
Negotiate56 = (unchecked((int)0x80000000))
}
internal enum NtlmAuthLevel
{
/* Use LM and NTLM, never use NTLMv2 session security. */
LM_and_NTLM,
/* Use NTLMv2 session security if the server supports it,
* otherwise fall back to LM and NTLM. */
LM_and_NTLM_and_try_NTLMv2_Session,
/* Use NTLMv2 session security if the server supports it,
* otherwise fall back to NTLM. Never use LM. */
NTLM_only,
/* Use NTLMv2 only. */
NTLMv2_only,
}
#endregion
#region internal structures
[StructLayout(LayoutKind.Sequential)]
internal struct SecurityHandle
{
internal IntPtr LowPart;
internal IntPtr HighPart;
internal SecurityHandle(int dummy)
{
LowPart = HighPart = IntPtr.Zero;
}
/// <summary>
/// Resets all internal pointers to default value
/// </summary>
internal void Reset()
{
LowPart = HighPart = IntPtr.Zero;
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct SecurityInteger
{
internal uint LowPart;
internal int HighPart;
internal SecurityInteger(int dummy)
{
LowPart = 0;
HighPart = 0;
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct SecurityBuffer : IDisposable
{
internal int cbBuffer;
internal int cbBufferType;
internal IntPtr pvBuffer;
internal SecurityBuffer(int bufferSize)
{
cbBuffer = bufferSize;
cbBufferType = (int)SecurityBufferType.SECBUFFER_TOKEN;
pvBuffer = Marshal.AllocHGlobal(bufferSize);
}
internal SecurityBuffer(byte[] secBufferBytes)
{
cbBuffer = secBufferBytes.Length;
cbBufferType = (int)SecurityBufferType.SECBUFFER_TOKEN;
pvBuffer = Marshal.AllocHGlobal(cbBuffer);
Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer);
}
internal SecurityBuffer(byte[] secBufferBytes, SecurityBufferType bufferType)
{
cbBuffer = secBufferBytes.Length;
cbBufferType = (int)bufferType;
pvBuffer = Marshal.AllocHGlobal(cbBuffer);
Marshal.Copy(secBufferBytes, 0, pvBuffer, cbBuffer);
}
public void Dispose()
{
if (pvBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(pvBuffer);
pvBuffer = IntPtr.Zero;
}
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct SecurityBufferDesciption : IDisposable
{
internal int ulVersion;
internal int cBuffers;
internal IntPtr pBuffers; //Point to SecBuffer
internal SecurityBufferDesciption(int bufferSize)
{
ulVersion = (int)SecurityBufferType.SECBUFFER_VERSION;
cBuffers = 1;
Common.SecurityBuffer ThisSecBuffer = new Common.SecurityBuffer(bufferSize);
pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer));
Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false);
}
internal SecurityBufferDesciption(byte[] secBufferBytes)
{
ulVersion = (int)SecurityBufferType.SECBUFFER_VERSION;
cBuffers = 1;
Common.SecurityBuffer ThisSecBuffer = new Common.SecurityBuffer(secBufferBytes);
pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(ThisSecBuffer));
Marshal.StructureToPtr(ThisSecBuffer, pBuffers, false);
}
internal SecurityBufferDesciption(BufferWrapper[] secBufferBytesArray)
{
if (secBufferBytesArray == null || secBufferBytesArray.Length == 0)
{
throw new ArgumentException("secBufferBytesArray cannot be null or 0 length");
}
ulVersion = (int)SecurityBufferType.SECBUFFER_VERSION;
cBuffers = secBufferBytesArray.Length;
//Allocate memory for SecBuffer Array....
pBuffers = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Buffer)) * cBuffers);
for (int Index = 0; Index < secBufferBytesArray.Length; Index++)
{
//Super hack: Now allocate memory for the individual SecBuffers
//and just copy the bit values to the SecBuffer array!!!
Common.SecurityBuffer ThisSecBuffer = new Common.SecurityBuffer(secBufferBytesArray[Index].Buffer, secBufferBytesArray[Index].BufferType);
//We will write out bits in the following order:
//int cbBuffer;
//int BufferType;
//pvBuffer;
//Note that we won't be releasing the memory allocated by ThisSecBuffer until we
//are disposed...
int CurrentOffset = Index * Marshal.SizeOf(typeof(Buffer));
Marshal.WriteInt32(pBuffers, CurrentOffset, ThisSecBuffer.cbBuffer);
Marshal.WriteInt32(pBuffers, CurrentOffset + Marshal.SizeOf(ThisSecBuffer.cbBuffer), ThisSecBuffer.cbBufferType);
Marshal.WriteIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(ThisSecBuffer.cbBuffer) + Marshal.SizeOf(ThisSecBuffer.cbBufferType), ThisSecBuffer.pvBuffer);
}
}
public void Dispose()
{
if (pBuffers != IntPtr.Zero)
{
if (cBuffers == 1)
{
Common.SecurityBuffer ThisSecBuffer = (Common.SecurityBuffer)Marshal.PtrToStructure(pBuffers, typeof(Common.SecurityBuffer));
ThisSecBuffer.Dispose();
}
else
{
for (int Index = 0; Index < cBuffers; Index++)
{
//The bits were written out the following order:
//int cbBuffer;
//int BufferType;
//pvBuffer;
//What we need to do here is to grab a hold of the pvBuffer allocate by the individual
//SecBuffer and release it...
int CurrentOffset = Index * Marshal.SizeOf(typeof(Buffer));
IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int)));
Marshal.FreeHGlobal(SecBufferpvBuffer);
}
}
Marshal.FreeHGlobal(pBuffers);
pBuffers = IntPtr.Zero;
}
}
internal byte[] GetBytes()
{
byte[] Buffer = null;
if (pBuffers == IntPtr.Zero)
{
throw new InvalidOperationException("Object has already been disposed!!!");
}
if (cBuffers == 1)
{
Common.SecurityBuffer ThisSecBuffer = (Common.SecurityBuffer)Marshal.PtrToStructure(pBuffers, typeof(Common.SecurityBuffer));
if (ThisSecBuffer.cbBuffer > 0)
{
Buffer = new byte[ThisSecBuffer.cbBuffer];
Marshal.Copy(ThisSecBuffer.pvBuffer, Buffer, 0, ThisSecBuffer.cbBuffer);
}
}
else
{
int BytesToAllocate = 0;
for (int Index = 0; Index < cBuffers; Index++)
{
//The bits were written out the following order:
//int cbBuffer;
//int BufferType;
//pvBuffer;
//What we need to do here calculate the total number of bytes we need to copy...
int CurrentOffset = Index * Marshal.SizeOf(typeof(Buffer));
BytesToAllocate += Marshal.ReadInt32(pBuffers, CurrentOffset);
}
Buffer = new byte[BytesToAllocate];
for (int Index = 0, BufferIndex = 0; Index < cBuffers; Index++)
{
//The bits were written out the following order:
//int cbBuffer;
//int BufferType;
//pvBuffer;
//Now iterate over the individual buffers and put them together into a
//byte array...
int CurrentOffset = Index * Marshal.SizeOf(typeof(Buffer));
int BytesToCopy = Marshal.ReadInt32(pBuffers, CurrentOffset);
IntPtr SecBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, CurrentOffset + Marshal.SizeOf(typeof(int)) + Marshal.SizeOf(typeof(int)));
Marshal.Copy(SecBufferpvBuffer, Buffer, BufferIndex, BytesToCopy);
BufferIndex += BytesToCopy;
}
}
return (Buffer);
}
}
#endregion
}
}
//
// Mono.Security.BitConverterLE.cs
// Like System.BitConverter but always little endian
//
// Author:
// Bernie Solomon
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
internal sealed class LittleEndian
{
private LittleEndian ()
{
}
unsafe private static byte[] GetUShortBytes (byte *bytes)
{
if (BitConverter.IsLittleEndian)
{
return new byte[] { bytes[0], bytes[1] };
}
else
{
return new byte[] { bytes[1], bytes[0] };
}
}
unsafe private static byte[] GetUIntBytes (byte *bytes)
{
if (BitConverter.IsLittleEndian)
{
return new byte[] { bytes[0], bytes[1], bytes[2], bytes[3] };
}
else
{
return new byte[] { bytes[3], bytes[2], bytes[1], bytes[0] };
}
}
unsafe private static byte[] GetULongBytes (byte *bytes)
{
if (BitConverter.IsLittleEndian)
{
return new byte[] { bytes [0], bytes [1], bytes [2], bytes [3],
bytes [4], bytes [5], bytes [6], bytes [7] };
}
else
{
return new byte[] { bytes [7], bytes [6], bytes [5], bytes [4],
bytes [3], bytes [2], bytes [1], bytes [0] };
}
}
unsafe internal static byte[] GetBytes (bool value)
{
return new byte [] { value ? (byte)1 : (byte)0 };
}
unsafe internal static byte[] GetBytes (char value)
{
return GetUShortBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (short value)
{
return GetUShortBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (int value)
{
return GetUIntBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (long value)
{
return GetULongBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (ushort value)
{
return GetUShortBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (uint value)
{
return GetUIntBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (ulong value)
{
return GetULongBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (float value)
{
return GetUIntBytes ((byte *) &value);
}
unsafe internal static byte[] GetBytes (double value)
{
return GetULongBytes ((byte *) &value);
}
unsafe private static void UShortFromBytes (byte *dst, byte[] src, int startIndex)
{
if (BitConverter.IsLittleEndian)
{
dst [0] = src [startIndex];
dst [1] = src [startIndex + 1];
}
else
{
dst [0] = src [startIndex + 1];
dst [1] = src [startIndex];
}
}
unsafe private static void UIntFromBytes (byte *dst, byte[] src, int startIndex)
{
if (BitConverter.IsLittleEndian)
{
dst [0] = src [startIndex];
dst [1] = src [startIndex + 1];
dst [2] = src [startIndex + 2];
dst [3] = src [startIndex + 3];
}
else
{
dst [0] = src [startIndex + 3];
dst [1] = src [startIndex + 2];
dst [2] = src [startIndex + 1];
dst [3] = src [startIndex];
}
}
unsafe private static void ULongFromBytes (byte *dst, byte[] src, int startIndex)
{
if (BitConverter.IsLittleEndian) {
for (int i = 0; i < 8; ++i)
dst [i] = src [startIndex + i];
} else {
for (int i = 0; i < 8; ++i)
dst [i] = src [startIndex + (7 - i)];
}
}
unsafe internal static bool ToBoolean (byte[] value, int startIndex)
{
return value [startIndex] != 0;
}
unsafe internal static char ToChar (byte[] value, int startIndex)
{
char ret;
UShortFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static short ToInt16 (byte[] value, int startIndex)
{
short ret;
UShortFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static int ToInt32 (byte[] value, int startIndex)
{
int ret;
UIntFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static long ToInt64 (byte[] value, int startIndex)
{
long ret;
ULongFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static ushort ToUInt16 (byte[] value, int startIndex)
{
ushort ret;
UShortFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static uint ToUInt32 (byte[] value, int startIndex)
{
uint ret;
UIntFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static ulong ToUInt64 (byte[] value, int startIndex)
{
ulong ret;
ULongFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static float ToSingle (byte[] value, int startIndex)
{
float ret;
UIntFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
unsafe internal static double ToDouble (byte[] value, int startIndex)
{
double ret;
ULongFromBytes ((byte *) &ret, value, startIndex);
return ret;
}
}
}
//
// Nancy.Authentication.Ntlm.Protocol.Type3Message - Authentication
//
// Author:
// Sebastien Pouliot <sebastien@ximian.com>
//
// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// References
// a. NTLM Authentication Scheme for HTTP, Ronald Tschalär
// http://www.innovation.ch/java/ntlm.html
// b. The NTLM Authentication Protocol, Copyright © 2003 Eric Glass
// http://davenport.sourceforge.net/ntlm.html
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
using System.Text;
internal class Message
{
static private byte[] header = { 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x00 };
internal Message (byte[] message)
{
_type = 3;
Decode (message);
}
/// <summary>
/// Domain name
/// </summary>
internal string Domain
{
get;
private set;
}
/// <summary>
/// Username
/// </summary>
internal string Username
{
get;
private set;
}
private int _type;
private Common.NtlmFlags _flags;
internal Common.NtlmFlags Flags
{
get { return _flags; }
set { _flags = value; }
}
// methods
private void Decode (byte[] message)
{
//base.Decode (message);
if (message == null)
throw new ArgumentNullException("message");
if (message.Length < 12)
{
string msg = "Minimum Type3 message length is 12 bytes.";
throw new ArgumentOutOfRangeException("message", message.Length, msg);
}
if (!CheckHeader(message))
{
string msg = "Invalid Type3 message header.";
throw new ArgumentException(msg, "message");
}
if (LittleEndian.ToUInt16 (message, 56) != message.Length)
{
string msg = "Invalid Type3 message length.";
throw new ArgumentException (msg, "message");
}
if (message.Length >= 64)
{
Flags = (Common.NtlmFlags)LittleEndian.ToUInt32(message, 60);
}
else
{
Flags = (Common.NtlmFlags)0x8201;
}
int dom_len = LittleEndian.ToUInt16 (message, 28);
int dom_off = LittleEndian.ToUInt16 (message, 32);
this.Domain = DecodeString (message, dom_off, dom_len);
int user_len = LittleEndian.ToUInt16 (message, 36);
int user_off = LittleEndian.ToUInt16 (message, 40);
this.Username = DecodeString (message, user_off, user_len);
}
string DecodeString (byte[] buffer, int offset, int len)
{
if ((Flags & Common.NtlmFlags.NegotiateUnicode) != 0)
{
return Encoding.Unicode.GetString(buffer, offset, len);
}
else
{
return Encoding.ASCII.GetString(buffer, offset, len);
}
}
protected bool CheckHeader(byte[] message)
{
for (int i = 0; i < header.Length; i++)
{
if (message[i] != header[i])
return false;
}
return (LittleEndian.ToUInt32(message, 8) == _type);
}
}
}
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
/// <summary>
/// Status of authenticated session
/// </summary>
internal class State
{
internal State()
{
this.Credentials = new Common.SecurityHandle(0);
this.Context = new Common.SecurityHandle(0);
this.LastSeen = DateTime.Now;
}
/// <summary>
/// Credentials used to validate NTLM hashes
/// </summary>
internal Common.SecurityHandle Credentials;
/// <summary>
/// Context will be used to validate HTLM hashes
/// </summary>
internal Common.SecurityHandle Context;
/// <summary>
/// Timestamp needed to calculate validity of the authenticated session
/// </summary>
internal DateTime LastSeen;
internal void ResetHandles()
{
this.Credentials.Reset();
this.Context.Reset();
}
internal void UpdatePresence()
{
this.LastSeen = DateTime.Now;
}
}
}
// http://pinvoke.net/default.aspx/secur32/InitializeSecurityContext.html
namespace Titanium.Web.Proxy.Network.WinAuth.Security
{
using System;
using System.Linq;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Principal;
using static Common;
using System.Threading.Tasks;
internal class WinAuthEndPoint
{
/// <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;
//null for initial call
SecurityBufferDesciption serverToken
= new SecurityBufferDesciption();
SecurityBufferDesciption clientToken
= new SecurityBufferDesciption(MaximumTokenSize);
try
{
int result;
var state = new State();
result = AcquireCredentialsHandle(
WindowsIdentity.GetCurrent().Name,
authScheme,
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,
IntPtr.Zero,
hostname,
StandardContextAttributes,
0,
SecurityNativeDataRepresentation,
ref serverToken,
0,
out state.Context,
out clientToken,
out NewContextAttributes,
out NewLifeTime);
if (result != IntermediateResult)
{
// Client challenge issue operation failed.
return null;
}
token = clientToken.GetBytes();
authStates.Add(requestId, state);
}
finally
{
clientToken.Dispose();
serverToken.Dispose();
}
return token;
}
/// <summary>
/// Acquire the final token to send
/// </summary>
/// <param name="hostname"></param>
/// <param name="serverChallenge"></param>
/// <param name="requestId"></param>
/// <returns></returns>
internal static byte[] AcquireFinalSecurityToken(string hostname,
byte[] serverChallenge, Guid requestId)
{
byte[] token = null;
//user server challenge
SecurityBufferDesciption serverToken
= new SecurityBufferDesciption(serverChallenge);
SecurityBufferDesciption clientToken
= new SecurityBufferDesciption(MaximumTokenSize);
try
{
int result;
var state = authStates[requestId];
state.UpdatePresence();
result = InitializeSecurityContext(ref state.Credentials,
ref state.Context,
hostname,
StandardContextAttributes,
0,
SecurityNativeDataRepresentation,
ref serverToken,
0,
out state.Context,
out clientToken,
out NewContextAttributes,
out NewLifeTime);
if (result != SuccessfulResult)
{
// Client challenge issue operation failed.
return null;
}
authStates.Remove(requestId);
token = clientToken.GetBytes();
}
finally
{
clientToken.Dispose();
serverToken.Dispose();
}
return token;
}
/// <summary>
/// Clear any hanging states
/// </summary>
/// <param name="stateCacheTimeOutMinutes"></param>
internal static async void ClearIdleStates(int stateCacheTimeOutMinutes)
{
var cutOff = DateTime.Now.AddMinutes(-1 * stateCacheTimeOutMinutes);
var outdated = authStates
.Where(x => x.Value.LastSeen < cutOff)
.ToList();
foreach (var cache in outdated)
{
authStates.Remove(cache.Key);
}
//after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60);
}
#region Native calls to secur32.dll
[DllImport("secur32.dll", SetLastError = true)]
static extern int InitializeSecurityContext(ref SecurityHandle phCredential,//PCredHandle
IntPtr phContext, //PCtxtHandle
string pszTargetName,
int fContextReq,
int Reserved1,
int TargetDataRep,
ref SecurityBufferDesciption pInput, //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", 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)]
private static extern int AcquireCredentialsHandle(
string pszPrincipal, //SEC_CHAR*
string pszPackage, //SEC_CHAR* //"Kerberos","NTLM","Negotiative"
int fCredentialUse,
IntPtr PAuthenticationID, //_LUID AuthenticationID,//pvLogonID, //PLUID
IntPtr pAuthData, //PVOID
int pGetKeyFn, //SEC_GET_KEY_FN
IntPtr pvGetKeyArgument, //PVOID
ref Common.SecurityHandle phCredential, //SecHandle //PCtxtHandle ref
ref Common.SecurityInteger ptsExpiry); //PTimeStamp //TimeStamp ref
#endregion
}
}
using Titanium.Web.Proxy.Network.WinAuth.Security;
using System;
namespace Titanium.Web.Proxy.Network.WinAuth
{
/// <summary>
/// A handler for NTLM/Kerberos windows authentication challenge from server
/// NTLM process details below
/// https://blogs.msdn.microsoft.com/chiranth/2013/09/20/ntlm-want-to-know-how-it-works/
/// </summary>
public static class WinAuthHandler
{
/// <summary>
/// Get the initial client token for server
/// using credentials of user running the proxy server process
/// </summary>
/// <param name="serverHostname"></param>
/// <param name="authScheme"></param>
/// <param name="requestId"></param>
/// <returns></returns>
public static string GetInitialAuthToken(string serverHostname,
string authScheme, Guid requestId)
{
var tokenBytes = WinAuthEndPoint.AcquireInitialSecurityToken(serverHostname, authScheme, requestId);
return string.Concat(" ", Convert.ToBase64String(tokenBytes));
}
/// <summary>
/// Get the final token given the server challenge token
/// </summary>
/// <param name="serverHostname"></param>
/// <param name="serverToken"></param>
/// <param name="requestId"></param>
/// <returns></returns>
public static string GetFinalAuthToken(string serverHostname,
string serverToken, Guid requestId)
{
var tokenBytes = WinAuthEndPoint.AcquireFinalSecurityToken(serverHostname,
Convert.FromBase64String(serverToken), requestId);
return string.Concat(" ", Convert.ToBase64String(tokenBytes));
}
}
}
...@@ -12,6 +12,7 @@ using Titanium.Web.Proxy.Helpers; ...@@ -12,6 +12,7 @@ using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network; using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -196,6 +197,15 @@ namespace Titanium.Web.Proxy ...@@ -196,6 +197,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>
...@@ -466,6 +476,11 @@ namespace Titanium.Web.Proxy ...@@ -466,6 +476,11 @@ namespace Titanium.Web.Proxy
CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes); CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes);
if (!RunTime.IsRunningOnMono())
{
WinAuthEndPoint.ClearIdleStates(2);
}
proxyRunning = true; proxyRunning = true;
} }
......
...@@ -36,6 +36,19 @@ namespace Titanium.Web.Proxy ...@@ -36,6 +36,19 @@ 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"
&& !RunTime.IsRunningOnMono())
{
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 +57,13 @@ namespace Titanium.Web.Proxy ...@@ -44,16 +57,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;
} }
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
<DocumentationFile>bin\Release\Titanium.Web.Proxy.XML</DocumentationFile> <DocumentationFile>bin\Release\Titanium.Web.Proxy.XML</DocumentationFile>
<DebugType>none</DebugType> <DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols> <DebugSymbols>false</DebugSymbols>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
...@@ -105,6 +106,13 @@ ...@@ -105,6 +106,13 @@
<Compile Include="Network\Tcp\TcpConnectionFactory.cs" /> <Compile Include="Network\Tcp\TcpConnectionFactory.cs" />
<Compile Include="Models\HttpHeader.cs" /> <Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.cs" /> <Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Network\WinAuth\Security\BufferWrapper.cs" />
<Compile Include="Network\WinAuth\Security\Common.cs" />
<Compile Include="Network\WinAuth\Security\WinAuthEndPoint.cs" />
<Compile Include="Network\WinAuth\Security\LittleEndian.cs" />
<Compile Include="Network\WinAuth\Security\Message.cs" />
<Compile Include="Network\WinAuth\Security\State.cs" />
<Compile Include="Network\WinAuth\WinAuthHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyAuthorizationHandler.cs" /> <Compile Include="ProxyAuthorizationHandler.cs" />
<Compile Include="RequestHandler.cs" /> <Compile Include="RequestHandler.cs" />
...@@ -119,6 +127,7 @@ ...@@ -119,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