Commit 6f0a3d90 authored by Honfika's avatar Honfika

prepare for HTTP/2

parent f528b8ec
......@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net45;netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>net45;netcoreapp2.0;netcoreapp2.1</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<LangVersion>7.1</LangVersion>
</PropertyGroup>
......
......@@ -3,6 +3,7 @@ using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended;
......@@ -10,6 +11,7 @@ using StreamExtended.Helpers;
using StreamExtended.Network;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
......@@ -100,6 +102,7 @@ namespace Titanium.Web.Proxy
//write back successfull CONNECT response
var response = ConnectResponse.CreateSuccessfullConnectResponse(version);
// Set ContentLength explicitly to properly handle HTTP 1.0
response.ContentLength = 0;
response.Headers.FixProxyHeaders();
......@@ -133,7 +136,21 @@ namespace Titanium.Web.Proxy
await CertificateManager.CreateCertificateAsync(certName);
//Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false);
var options = new SslServerAuthenticationOptions();
options.ApplicationProtocols = clientHelloInfo.GetAlpn();
if (options.ApplicationProtocols == null || options.ApplicationProtocols.Count == 0)
{
options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList;
}
// client connection is always HTTP 1.x, todo
options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList;
options.ServerCertificate = certificate;
options.ClientCertificateRequired = false;
options.EnabledSslProtocols = SupportedSslProtocols;
options.CertificateRevocationCheckMode = X509RevocationMode.NoCheck;
await sslStream.AuthenticateAsServerAsync(options, new CancellationToken(false));
//HTTPS server created - we can now decrypt the client's traffic
clientStream = new CustomBufferedStream(sslStream, BufferSize);
......
using StreamExtended;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended;
namespace Titanium.Web.Proxy.Extensions
{
internal static class SslExtensions
{
public static readonly List<SslApplicationProtocol> Http11ProtocolAsList = new List<SslApplicationProtocol> { SslApplicationProtocol.Http11 };
public static string GetServerName(this ClientHelloInfo clientHelloInfo)
{
if (clientHelloInfo.Extensions != null &&
......@@ -14,5 +25,97 @@ namespace Titanium.Web.Proxy.Extensions
return null;
}
#if NETCOREAPP2_1
public static List<SslApplicationProtocol> GetAlpn(this ClientHelloInfo clientHelloInfo)
{
if (clientHelloInfo.Extensions != null && clientHelloInfo.Extensions.TryGetValue("ALPN", out var alpnExtension))
{
var alpn = alpnExtension.Data.Split(',');
if (alpn.Length != 0)
{
var result = new List<SslApplicationProtocol>(alpn.Length);
foreach (string p in alpn)
{
string protocol = p.Trim();
if (protocol.Equals("http/1.1"))
{
result.Add(SslApplicationProtocol.Http11);
}
else if (protocol.Equals("h2"))
{
result.Add(SslApplicationProtocol.Http2);
}
}
return result;
}
}
return null;
}
#else
public static List<SslApplicationProtocol> GetAlpn(this ClientHelloInfo clientHelloInfo)
{
return Http11ProtocolAsList;
}
public static Task AuthenticateAsClientAsync(this SslStream sslStream, SslClientAuthenticationOptions option, CancellationToken token)
{
return sslStream.AuthenticateAsClientAsync(option.TargetHost, option.ClientCertificates, option.EnabledSslProtocols, option.CertificateRevocationCheckMode != X509RevocationMode.NoCheck);
}
public static Task AuthenticateAsServerAsync(this SslStream sslStream, SslServerAuthenticationOptions options, CancellationToken token)
{
return sslStream.AuthenticateAsServerAsync(options.ServerCertificate, options.ClientCertificateRequired, options.EnabledSslProtocols, options.CertificateRevocationCheckMode != X509RevocationMode.NoCheck);
}
#endif
}
#if !NETCOREAPP2_1
public enum SslApplicationProtocol
{
Http11, Http2
}
public class SslClientAuthenticationOptions
{
public bool AllowRenegotiation { get; set; }
public string TargetHost { get; set; }
public X509CertificateCollection ClientCertificates { get; set; }
public LocalCertificateSelectionCallback LocalCertificateSelectionCallback { get; set; }
public SslProtocols EnabledSslProtocols { get; set; }
public X509RevocationMode CertificateRevocationCheckMode { get; set; }
public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
public EncryptionPolicy EncryptionPolicy { get; set; }
}
public class SslServerAuthenticationOptions
{
public bool AllowRenegotiation { get; set; }
public X509Certificate ServerCertificate { get; set; }
public bool ClientCertificateRequired { get; set; }
public SslProtocols EnabledSslProtocols { get; set; }
public X509RevocationMode CertificateRevocationCheckMode { get; set; }
public List<SslApplicationProtocol> ApplicationProtocols { get; set; }
public RemoteCertificateValidationCallback RemoteCertificateValidationCallback { get; set; }
public EncryptionPolicy EncryptionPolicy { get; set; }
}
#endif
}
......@@ -66,6 +66,7 @@ namespace Titanium.Web.Proxy.Extensions
}
catch
{
// ignored
}
}
}
......
......@@ -17,7 +17,9 @@ namespace Titanium.Web.Proxy.Models
internal static readonly Version Version11 = new Version(1, 1);
internal static HttpHeader ProxyConnectionKeepAlive = new HttpHeader("Proxy-Connection", "keep-alive");
internal static readonly Version Version20 = new Version(2, 0);
internal static readonly HttpHeader ProxyConnectionKeepAlive = new HttpHeader("Proxy-Connection", "keep-alive");
/// <summary>
/// Constructor.
......
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using StreamExtended.Network;
using Titanium.Web.Proxy.Extensions;
......@@ -22,6 +24,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
/// <param name="remoteHostName"></param>
/// <param name="remotePort"></param>
/// <param name="applicationProtocols"></param>
/// <param name="httpVersion"></param>
/// <param name="isHttps"></param>
/// <param name="isConnect"></param>
......@@ -29,8 +32,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="upStreamEndPoint"></param>
/// <param name="externalProxy"></param>
/// <returns></returns>
internal async Task<TcpConnection> CreateClient(string remoteHostName,
int remotePort, Version httpVersion, bool isHttps, bool isConnect,
internal async Task<TcpConnection> CreateClient(string remoteHostName, int remotePort,
List<SslApplicationProtocol> applicationProtocols, Version httpVersion, bool isHttps, bool isConnect,
ProxyServer proxyServer, IPEndPoint upStreamEndPoint, ExternalProxy externalProxy)
{
bool useUpstreamProxy = false;
......@@ -106,8 +109,21 @@ namespace Titanium.Web.Proxy.Network.Tcp
proxyServer.SelectClientCertificate);
stream = new CustomBufferedStream(sslStream, proxyServer.BufferSize);
await sslStream.AuthenticateAsClientAsync(remoteHostName, null, proxyServer.SupportedSslProtocols,
proxyServer.CheckCertificateRevocation);
var options = new SslClientAuthenticationOptions();
options.ApplicationProtocols = applicationProtocols;
if (options.ApplicationProtocols == null || options.ApplicationProtocols.Count == 0)
{
options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList;
}
// server connection is always HTTP 1.x, todo
options.ApplicationProtocols = SslExtensions.Http11ProtocolAsList;
options.TargetHost = remoteHostName;
options.ClientCertificates = null;
options.EnabledSslProtocols = proxyServer.SupportedSslProtocols;
options.CertificateRevocationCheckMode = proxyServer.CheckCertificateRevocation;
await sslStream.AuthenticateAsClientAsync(options, new CancellationToken(false));
}
client.ReceiveTimeout = proxyServer.ConnectionTimeOutSeconds * 1000;
......
......@@ -4,6 +4,7 @@ using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
......@@ -125,9 +126,9 @@ namespace Titanium.Web.Proxy
/// <summary>
/// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance (Default disabled)
/// Note: If enabled can reduce performance (Default: NoCheck)
/// </summary>
public bool CheckCertificateRevocation { get; set; }
public X509RevocationMode CheckCertificateRevocation { get; set; }
/// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
......
......@@ -57,6 +57,21 @@ namespace Titanium.Web.Proxy
{
// read the request line
string httpCmd = await clientStreamReader.ReadLineAsync();
if (httpCmd == "PRI * HTTP/2.0")
{
// HTTP/2 Connection Preface
string line = await clientStreamReader.ReadLineAsync();
if (line != string.Empty) throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received");
line = await clientStreamReader.ReadLineAsync();
if (line != "SM") throw new Exception($"HTTP/2 Protocol violation. 'SM' expected, '{line}' received");
line = await clientStreamReader.ReadLineAsync();
if (line != string.Empty) throw new Exception($"HTTP/2 Protocol violation. Empty string expected, '{line}' received");
// todo
}
if (string.IsNullOrEmpty(httpCmd))
{
return;
......@@ -362,12 +377,12 @@ namespace Titanium.Web.Proxy
args.CustomUpStreamProxyUsed = customUpStreamProxy;
return await tcpConnectionFactory.CreateClient(args.WebSession.Request.RequestUri.Host,
return await tcpConnectionFactory.CreateClient(
args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port,
args.WebSession.Request.HttpVersion,
isHttps,
isConnect, this,
args.WebSession.UpStreamEndPoint ?? UpStreamEndPoint,
args.WebSession.ConnectRequest?.ClientHelloInfo?.GetAlpn(),
args.WebSession.Request.HttpVersion, isHttps, isConnect,
this, args.WebSession.UpStreamEndPoint ?? UpStreamEndPoint,
customUpStreamProxy ?? (isHttps ? UpStreamHttpsProxy : UpStreamHttpProxy));
}
......
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net45;netstandard2.0;netcoreapp2.1</TargetFrameworks>
<RootNamespace>Titanium.Web.Proxy</RootNamespace>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<SignAssembly>True</SignAssembly>
......@@ -25,4 +25,13 @@
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="Microsoft.Win32.Registry">
<Version>4.4.0</Version>
</PackageReference>
<PackageReference Include="System.Security.Principal.Windows">
<Version>4.4.1</Version>
</PackageReference>
</ItemGroup>
</Project>
\ No newline at end of file
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