Commit 255edefd authored by Björn Weström's avatar Björn Weström Committed by GitHub

Merge pull request #1 from justcoding121/develop

Merge to latest
parents dbe023ba 4b2f944a
...@@ -78,7 +78,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -78,7 +78,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//proxyServer.EnableWinAuth = true; //proxyServer.EnableWinAuth = true;
explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true) explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000)
{ {
//You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown //You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown
......
...@@ -483,7 +483,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -483,7 +483,6 @@ namespace Titanium.Web.Proxy.EventArguments
} }
request.Body = body; request.Body = body;
request.UpdateContentLength();
} }
/// <summary> /// <summary>
......
...@@ -14,5 +14,10 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -14,5 +14,10 @@ namespace Titanium.Web.Proxy.Extensions
{ {
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0; return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase) >= 0;
} }
internal static int IndexOfIgnoreCase(this string str, string value)
{
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase);
}
} }
} }
...@@ -102,6 +102,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -102,6 +102,11 @@ namespace Titanium.Web.Proxy.Http
internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true) internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
{ {
if (BodyInternal != null)
{
return;
}
//GET request don't have a request body to read //GET request don't have a request body to read
if (!HasBody) if (!HasBody)
{ {
......
...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary> /// <summary>
/// Cached body content as byte array /// Cached body content as byte array
/// </summary> /// </summary>
private byte[] body; protected byte[] BodyInternal;
/// <summary> /// <summary>
/// Cached body as string /// Cached body as string
...@@ -127,11 +127,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -127,11 +127,11 @@ namespace Titanium.Web.Proxy.Http
get get
{ {
EnsureBodyAvailable(); EnsureBodyAvailable();
return body; return BodyInternal;
} }
internal set internal set
{ {
body = value; BodyInternal = value;
bodyString = null; bodyString = null;
//If there is a content length header update it //If there is a content length header update it
...@@ -173,6 +173,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -173,6 +173,11 @@ namespace Titanium.Web.Proxy.Http
internal byte[] CompressBodyAndUpdateContentLength() internal byte[] CompressBodyAndUpdateContentLength()
{ {
if (!IsBodyRead && BodyInternal == null)
{
return null;
}
bool isChunked = IsChunked; bool isChunked = IsChunked;
string contentEncoding = ContentEncoding; string contentEncoding = ContentEncoding;
...@@ -219,7 +224,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -219,7 +224,7 @@ namespace Titanium.Web.Proxy.Http
internal void UpdateContentLength() internal void UpdateContentLength()
{ {
ContentLength = IsChunked ? -1 : body?.Length ?? 0; ContentLength = IsChunked ? -1 : BodyInternal?.Length ?? 0;
} }
/// <summary> /// <summary>
...@@ -229,7 +234,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -229,7 +234,7 @@ namespace Titanium.Web.Proxy.Http
{ {
if (!KeepBody) if (!KeepBody)
{ {
body = null; BodyInternal = null;
bodyString = null; bodyString = null;
} }
} }
......
...@@ -81,6 +81,11 @@ namespace Titanium.Web.Proxy.Http ...@@ -81,6 +81,11 @@ namespace Titanium.Web.Proxy.Http
internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true) internal override void EnsureBodyAvailable(bool throwWhenNotReadYet = true)
{ {
if (BodyInternal != null)
{
return;
}
if (!IsBodyRead && throwWhenNotReadYet) if (!IsBodyRead && throwWhenNotReadYet)
{ {
throw new Exception("Response body is not read yet. " + throw new Exception("Response body is not read yet. " +
...@@ -123,6 +128,21 @@ namespace Titanium.Web.Proxy.Http ...@@ -123,6 +128,21 @@ namespace Titanium.Web.Proxy.Http
} }
} }
/// <summary>
/// Constructor.
/// </summary>
public Response()
{
}
/// <summary>
/// Constructor.
/// </summary>
public Response(byte[] body)
{
Body = body;
}
internal static string CreateResponseLine(Version version, int statusCode, string statusDescription) internal static string CreateResponseLine(Version version, int statusCode, string statusDescription)
{ {
return $"HTTP/{version.Major}.{version.Minor} {statusCode} {statusDescription}"; return $"HTTP/{version.Major}.{version.Minor} {statusCode} {statusDescription}";
......
...@@ -15,5 +15,13 @@ namespace Titanium.Web.Proxy.Http.Responses ...@@ -15,5 +15,13 @@ namespace Titanium.Web.Proxy.Http.Responses
StatusCode = (int)HttpStatusCode.OK; StatusCode = (int)HttpStatusCode.OK;
StatusDescription = "OK"; StatusDescription = "OK";
} }
/// <summary>
/// Constructor.
/// </summary>
public OkResponse(byte[] body) : this()
{
Body = body;
}
} }
} }
...@@ -17,6 +17,11 @@ namespace Titanium.Web.Proxy.Models ...@@ -17,6 +17,11 @@ namespace Titanium.Web.Proxy.Models
internal bool IsSystemHttpsProxy { get; set; } internal bool IsSystemHttpsProxy { get; set; }
/// <summary>
/// Enable SSL?
/// </summary>
public bool DecryptSsl { get; }
/// <summary> /// <summary>
/// Generic certificate to use for SSL decryption. /// Generic certificate to use for SSL decryption.
/// </summary> /// </summary>
...@@ -40,9 +45,10 @@ namespace Titanium.Web.Proxy.Models ...@@ -40,9 +45,10 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
/// <param name="ipAddress"></param> /// <param name="ipAddress"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="enableSsl"></param> /// <param name="decryptSsl"></param>
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) : base(ipAddress, port, enableSsl) public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool decryptSsl = true) : base(ipAddress, port)
{ {
this.DecryptSsl = decryptSsl;
} }
internal async Task InvokeBeforeTunnelConnectRequest(ProxyServer proxyServer, TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc) internal async Task InvokeBeforeTunnelConnectRequest(ProxyServer proxyServer, TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc)
......
...@@ -17,11 +17,10 @@ namespace Titanium.Web.Proxy.Models ...@@ -17,11 +17,10 @@ namespace Titanium.Web.Proxy.Models
/// <param name="ipAddress"></param> /// <param name="ipAddress"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="enableSsl"></param> /// <param name="enableSsl"></param>
protected ProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) protected ProxyEndPoint(IPAddress ipAddress, int port)
{ {
IpAddress = ipAddress; IpAddress = ipAddress;
Port = port; Port = port;
EnableSsl = enableSsl;
} }
/// <summary> /// <summary>
...@@ -39,10 +38,6 @@ namespace Titanium.Web.Proxy.Models ...@@ -39,10 +38,6 @@ namespace Titanium.Web.Proxy.Models
/// </summary> /// </summary>
public int Port { get; internal set; } public int Port { get; internal set; }
/// <summary>
/// Enable SSL?
/// </summary>
public bool EnableSsl { get; }
/// <summary> /// <summary>
/// Is IPv6 enabled? /// Is IPv6 enabled?
......
...@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy.Models ...@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy.Models
/// <param name="ipAddress"></param> /// <param name="ipAddress"></param>
/// <param name="port"></param> /// <param name="port"></param>
/// <param name="enableSsl"></param> /// <param name="enableSsl"></param>
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) : base(ipAddress, port, enableSsl) public TransparentProxyEndPoint(IPAddress ipAddress, int port) : base(ipAddress, port)
{ {
GenericCertificateName = "localhost"; GenericCertificateName = "localhost";
} }
......
...@@ -7,12 +7,24 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -7,12 +7,24 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
/// </summary> /// </summary>
internal class State internal class State
{ {
/// <summary>
/// States during Windows Authentication
/// </summary>
public enum WinAuthState
{
UNAUTHORIZED,
INITIAL_TOKEN,
FINAL_TOKEN,
AUTHORIZED
};
internal State() internal State()
{ {
Credentials = new Common.SecurityHandle(0); Credentials = new Common.SecurityHandle(0);
Context = new Common.SecurityHandle(0); Context = new Common.SecurityHandle(0);
LastSeen = DateTime.Now; LastSeen = DateTime.Now;
AuthState = WinAuthState.UNAUTHORIZED;
} }
/// <summary> /// <summary>
...@@ -30,10 +42,16 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -30,10 +42,16 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
/// </summary> /// </summary>
internal DateTime LastSeen; internal DateTime LastSeen;
/// <summary>
/// Current state of the authentication process
/// </summary>
internal WinAuthState AuthState;
internal void ResetHandles() internal void ResetHandles()
{ {
Credentials.Reset(); Credentials.Reset();
Context.Reset(); Context.Reset();
AuthState = WinAuthState.UNAUTHORIZED;
} }
internal void UpdatePresence() internal void UpdatePresence()
......
...@@ -38,11 +38,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -38,11 +38,9 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
try try
{ {
int result;
var state = new State(); var state = new State();
result = AcquireCredentialsHandle( int result = AcquireCredentialsHandle(
WindowsIdentity.GetCurrent().Name, WindowsIdentity.GetCurrent().Name,
authScheme, authScheme,
SecurityCredentialsOutbound, SecurityCredentialsOutbound,
...@@ -79,6 +77,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -79,6 +77,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
return null; return null;
} }
state.AuthState = State.WinAuthState.INITIAL_TOKEN;
token = clientToken.GetBytes(); token = clientToken.GetBytes();
authStates.Add(requestId, state); authStates.Add(requestId, state);
} }
...@@ -109,13 +108,11 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -109,13 +108,11 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
try try
{ {
int result;
var state = authStates[requestId]; var state = authStates[requestId];
state.UpdatePresence(); state.UpdatePresence();
result = InitializeSecurityContext(ref state.Credentials, int result = InitializeSecurityContext(ref state.Credentials,
ref state.Context, ref state.Context,
hostname, hostname,
StandardContextAttributes, StandardContextAttributes,
...@@ -135,7 +132,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -135,7 +132,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
return null; return null;
} }
authStates.Remove(requestId); state.AuthState = State.WinAuthState.FINAL_TOKEN;
token = clientToken.GetBytes(); token = clientToken.GetBytes();
} }
finally finally
...@@ -166,6 +163,49 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -166,6 +163,49 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
await Task.Delay(1000 * 60); await Task.Delay(1000 * 60);
} }
/// <summary>
/// Validates that the current WinAuth state of the connection matches the
/// expectation, used to detect failed authentication
/// </summary>
/// <param name="requestId"></param>
/// <param name="expectedAuthState"></param>
/// <returns></returns>
internal static bool ValidateWinAuthState(Guid requestId, State.WinAuthState expectedAuthState)
{
var stateExists = authStates.TryGetValue(requestId, out var state);
if (expectedAuthState == State.WinAuthState.UNAUTHORIZED)
{
// Validation before initial token
return stateExists == false ||
state.AuthState == State.WinAuthState.UNAUTHORIZED ||
state.AuthState == State.WinAuthState.AUTHORIZED; // Server may require re-authentication on an open connection
}
if (expectedAuthState == State.WinAuthState.INITIAL_TOKEN)
{
// Validation before final token
return stateExists &&
(state.AuthState == State.WinAuthState.INITIAL_TOKEN ||
state.AuthState == State.WinAuthState.AUTHORIZED); // Server may require re-authentication on an open connection
}
throw new Exception("Unsupported validation of WinAuthState");
}
/// <summary>
/// Set the AuthState to authorized and update the connection state lifetime
/// </summary>
/// <param name="requestId"></param>
internal static void AuthenticatedResponse(Guid requestId)
{
if (authStates.TryGetValue(requestId, out var state))
{
state.AuthState = State.WinAuthState.AUTHORIZED;
state.UpdatePresence();
}
}
#region Native calls to secur32.dll #region Native calls to secur32.dll
[DllImport("secur32.dll", SetLastError = true)] [DllImport("secur32.dll", SetLastError = true)]
......
...@@ -316,13 +316,9 @@ namespace Titanium.Web.Proxy ...@@ -316,13 +316,9 @@ namespace Titanium.Web.Proxy
bool isHttp = (protocolType & ProxyProtocolType.Http) > 0; bool isHttp = (protocolType & ProxyProtocolType.Http) > 0;
bool isHttps = (protocolType & ProxyProtocolType.Https) > 0; bool isHttps = (protocolType & ProxyProtocolType.Https) > 0;
if (isHttps) if (isHttps)
{ {
if (!endPoint.EnableSsl)
{
throw new Exception("Endpoint do not support Https connections");
}
CertificateManager.EnsureRootCertificate(); CertificateManager.EnsureRootCertificate();
......
...@@ -79,7 +79,7 @@ namespace Titanium.Web.Proxy ...@@ -79,7 +79,7 @@ namespace Titanium.Web.Proxy
await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc); await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc);
//filter out excluded host names //filter out excluded host names
bool excluded = connectArgs.Excluded; bool excluded = !endPoint.DecryptSsl || connectArgs.Excluded;
if (await CheckAuthorization(connectArgs) == false) if (await CheckAuthorization(connectArgs) == false)
{ {
...@@ -97,7 +97,8 @@ namespace Titanium.Web.Proxy ...@@ -97,7 +97,8 @@ namespace Titanium.Web.Proxy
await clientStreamWriter.WriteResponseAsync(response); await clientStreamWriter.WriteResponseAsync(response);
var clientHelloInfo = await SslTools.PeekClientHello(clientStream); var clientHelloInfo = await SslTools.PeekClientHello(clientStream);
bool isClientHello = clientHelloInfo != null; bool isClientHello = clientHelloInfo != null;
if (isClientHello) if (isClientHello)
{ {
...@@ -130,7 +131,7 @@ namespace Titanium.Web.Proxy ...@@ -130,7 +131,7 @@ namespace Titanium.Web.Proxy
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize); clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
} }
catch(Exception e) catch (Exception e)
{ {
ExceptionFunc(new Exception($"Could'nt authenticate client '{connectHostname}' with fake certificate.", e)); ExceptionFunc(new Exception($"Could'nt authenticate client '{connectHostname}' with fake certificate.", e));
sslStream?.Dispose(); sslStream?.Dispose();
...@@ -227,37 +228,46 @@ namespace Titanium.Web.Proxy ...@@ -227,37 +228,46 @@ namespace Titanium.Web.Proxy
try try
{ {
if (endPoint.EnableSsl) var clientHelloInfo = await SslTools.PeekClientHello(clientStream);
{
var clientHelloInfo = await SslTools.PeekClientHello(clientStream); var isHttps = clientHelloInfo != null;
string httpsHostName = null;
if (clientHelloInfo != null) if (isHttps)
{
SslStream sslStream = null;
try
{ {
var sslStream = new SslStream(clientStream); sslStream = new SslStream(clientStream);
clientStream = new CustomBufferedStream(sslStream, BufferSize);
string sniHostName = clientHelloInfo.GetServerName() ?? endPoint.GenericCertificateName; httpsHostName = clientHelloInfo.GetServerName() ?? endPoint.GenericCertificateName;
string certName = HttpHelper.GetWildCardDomainName(sniHostName); string certName = HttpHelper.GetWildCardDomainName(httpsHostName);
var certificate = await CertificateManager.CreateCertificateAsync(certName); var certificate = await CertificateManager.CreateCertificateAsync(certName);
try
{
//Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
}
catch (Exception e)
{
ExceptionFunc(new Exception($"Could'nt authenticate client '{sniHostName}' with fake certificate.", e));
return;
}
}
//HTTPS server created - we can now decrypt the client's traffic //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
//HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferSize);
clientStreamReader.Dispose();
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
}
catch (Exception e)
{
ExceptionFunc(new Exception($"Could'nt authenticate client '{httpsHostName}' with fake certificate.", e));
sslStream?.Dispose();
return;
}
} }
//HTTPS server created - we can now decrypt the client's traffic
//Now create the request //Now create the request
await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter,
endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null, true); isHttps ? httpsHostName : null, endPoint, null, true);
} }
finally finally
{ {
...@@ -605,7 +615,7 @@ namespace Titanium.Web.Proxy ...@@ -605,7 +615,7 @@ namespace Titanium.Web.Proxy
/// <param name="requestHeaders"></param> /// <param name="requestHeaders"></param>
private void PrepareRequestHeaders(HeaderCollection requestHeaders) private void PrepareRequestHeaders(HeaderCollection requestHeaders)
{ {
if(requestHeaders.HeaderExists(KnownHeaders.AcceptEncoding)) if (requestHeaders.HeaderExists(KnownHeaders.AcceptEncoding))
{ {
requestHeaders.SetOrAddHeaderValue(KnownHeaders.AcceptEncoding, "gzip,deflate"); requestHeaders.SetOrAddHeaderValue(KnownHeaders.AcceptEncoding, "gzip,deflate");
} }
......
...@@ -7,6 +7,7 @@ using Titanium.Web.Proxy.Compression; ...@@ -7,6 +7,7 @@ using Titanium.Web.Proxy.Compression;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions; using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -28,14 +29,22 @@ namespace Titanium.Web.Proxy ...@@ -28,14 +29,22 @@ namespace Titanium.Web.Proxy
await args.WebSession.ReceiveResponse(); await args.WebSession.ReceiveResponse();
var response = args.WebSession.Response; var response = args.WebSession.Response;
args.ReRequest = false;
//check for windows authentication //check for windows authentication
if (isWindowsAuthenticationEnabledAndSupported && response.StatusCode == (int)HttpStatusCode.Unauthorized) if (isWindowsAuthenticationEnabledAndSupported)
{ {
await Handle401UnAuthorized(args); if (response.StatusCode == (int)HttpStatusCode.Unauthorized)
{
await Handle401UnAuthorized(args);
}
else
{
WinAuthEndPoint.AuthenticatedResponse(args.WebSession.RequestId);
}
} }
args.ReRequest = false; response.OriginalHasBody = response.HasBody;
//if user requested call back then do it //if user requested call back then do it
if (!response.Locked) if (!response.Locked)
......
...@@ -3,30 +3,32 @@ using System.Collections.Generic; ...@@ -3,30 +3,32 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Http; using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.WinAuth; using Titanium.Web.Proxy.Network.WinAuth;
using Titanium.Web.Proxy.Network.WinAuth.Security;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
public partial class ProxyServer public partial class ProxyServer
{ {
//possible header names //possible header names
private static readonly List<string> authHeaderNames = new List<string> private static readonly HashSet<string> authHeaderNames = new HashSet<string>
{ {
"WWW-Authenticate", "WWW-Authenticate".ToLower(),
//IIS 6.0 messed up names below //IIS 6.0 messed up names below
"WWWAuthenticate", "WWWAuthenticate".ToLower(),
"NTLMAuthorization", "NTLMAuthorization".ToLower(),
"NegotiateAuthorization", "NegotiateAuthorization".ToLower(),
"KerberosAuthorization" "KerberosAuthorization".ToLower()
}; };
private static readonly List<string> authSchemes = new List<string> private static readonly HashSet<string> authSchemes = new HashSet<string>
{ {
"NTLM", "NTLM".ToLower(),
"Negotiate", "Negotiate".ToLower(),
"Kerberos" "Kerberos".ToLower()
}; };
/// <summary> /// <summary>
...@@ -47,7 +49,7 @@ namespace Titanium.Web.Proxy ...@@ -47,7 +49,7 @@ namespace Titanium.Web.Proxy
//check in non-unique headers first //check in non-unique headers first
var header = response.Headers.NonUniqueHeaders.FirstOrDefault( var header = response.Headers.NonUniqueHeaders.FirstOrDefault(
x => authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase))); x => authHeaderNames.Contains(x.Key.ToLower()));
if (!header.Equals(new KeyValuePair<string, List<HttpHeader>>())) if (!header.Equals(new KeyValuePair<string, List<HttpHeader>>()))
{ {
...@@ -63,8 +65,10 @@ namespace Titanium.Web.Proxy ...@@ -63,8 +65,10 @@ namespace Titanium.Web.Proxy
//check in unique headers //check in unique headers
if (authHeader == null) if (authHeader == null)
{ {
headerName = null;
//check in non-unique headers first //check in non-unique headers first
var uHeader = response.Headers.Headers.FirstOrDefault(x => authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase))); var uHeader = response.Headers.Headers.FirstOrDefault(x => authHeaderNames.Contains(x.Key.ToLower()));
if (!uHeader.Equals(new KeyValuePair<string, HttpHeader>())) if (!uHeader.Equals(new KeyValuePair<string, HttpHeader>()))
{ {
...@@ -82,7 +86,16 @@ namespace Titanium.Web.Proxy ...@@ -82,7 +86,16 @@ namespace Titanium.Web.Proxy
if (authHeader != null) if (authHeader != null)
{ {
string scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase)); string scheme = authSchemes.Contains(authHeader.Value.ToLower()) ? authHeader.Value.ToLower() : null;
var expectedAuthState = scheme == null ? State.WinAuthState.INITIAL_TOKEN : State.WinAuthState.UNAUTHORIZED;
if (!WinAuthEndPoint.ValidateWinAuthState(args.WebSession.RequestId, expectedAuthState))
{
// Invalid state, create proper error message to client
await RewriteUnauthorizedResponse(args);
return;
}
var request = args.WebSession.Request; var request = args.WebSession.Request;
...@@ -98,7 +111,7 @@ namespace Titanium.Web.Proxy ...@@ -98,7 +111,7 @@ namespace Titanium.Web.Proxy
//replace existing authorization header if any //replace existing authorization header if any
request.Headers.SetOrAddHeaderValue(KnownHeaders.Authorization, auth); request.Headers.SetOrAddHeaderValue(KnownHeaders.Authorization, auth);
//don't need to send body for Authorization request //don't need to send body for Authorization request
if (request.HasBody) if (request.HasBody)
{ {
...@@ -108,7 +121,7 @@ namespace Titanium.Web.Proxy ...@@ -108,7 +121,7 @@ namespace Titanium.Web.Proxy
//challenge value will start with any of the scheme selected //challenge value will start with any of the scheme selected
else else
{ {
scheme = authSchemes.FirstOrDefault(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) && scheme = authSchemes.First(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) &&
authHeader.Value.Length > x.Length + 1); authHeader.Value.Length > x.Length + 1);
string serverToken = authHeader.Value.Substring(scheme.Length + 1); string serverToken = authHeader.Value.Substring(scheme.Length + 1);
...@@ -130,13 +143,50 @@ namespace Titanium.Web.Proxy ...@@ -130,13 +143,50 @@ namespace Titanium.Web.Proxy
//Should we cache all Set-Cokiee headers from server during auth process //Should we cache all Set-Cokiee headers from server during auth process
//and send it to client after auth? //and send it to client after auth?
//clear current server response // Let ResponseHandler send the updated request
await args.ClearResponse(); args.ReRequest = true;
}
}
/// <summary>
/// Rewrites the response body for failed authentication
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
internal async Task RewriteUnauthorizedResponse(SessionEventArgs args)
{
var response = args.WebSession.Response;
// Strip authentication headers to avoid credentials prompt in client web browser
foreach (var authHeaderName in authHeaderNames)
{
response.Headers.RemoveHeader(authHeaderName);
}
//request again with updated authorization header // Add custom div to body to clarify that the proxy (not the client browser) failed authentication
//and server cookies string authErrorMessage = "<div class=\"inserted-by-proxy\"><h2>NTLM authentication through Titanium.Web.Proxy (" +
await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args); args.ProxyClient.TcpClient.Client.LocalEndPoint +
") failed. Please check credentials.</h2></div>";
string originalErrorMessage = "<div class=\"inserted-by-proxy\"><h3>Response from remote web server below.</h3></div><br/>";
string body = await args.GetResponseBodyAsString();
int idx = body.IndexOfIgnoreCase("<body>");
if (idx >= 0)
{
var bodyPos = idx + "<body>".Length;
body = body.Insert(bodyPos, authErrorMessage + originalErrorMessage);
} }
else
{
// Cannot parse response body, replace it
body = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\">" +
"<body>" +
authErrorMessage +
"</body>" +
"</html>";
}
args.SetResponseBodyString(body);
} }
} }
} }
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