From 7ccbdd92908b0fcd43d02c06fa00ab010bbaa1e6 Mon Sep 17 00:00:00 2001 From: honfika Date: Mon, 26 Dec 2022 00:43:36 +0100 Subject: [PATCH] tls improvements --- .../ExplicitClientHandler.cs | 4 +- .../Extensions/SslExtensions.cs | 86 ++- .../Extensions/StringExtensions.cs | 30 + .../Network/Models/SslExtension.cs | 625 +++++++++++++++++- .../Network/Readers/PeekStreamReader.cs | 8 +- .../Network/Ssl/ClientHelloInfo.cs | 19 +- .../Network/Ssl/ServerHelloInfo.cs | 5 +- .../Network/Ssl/SslExtensions.cs | 432 ------------ .../Network/Ssl/SslTools.cs | 16 +- src/Titanium.Web.Proxy/ProxyServer.cs | 6 +- .../TransparentClientHandler.cs | 4 +- 11 files changed, 761 insertions(+), 474 deletions(-) delete mode 100644 src/Titanium.Web.Proxy/Network/Ssl/SslExtensions.cs diff --git a/src/Titanium.Web.Proxy/ExplicitClientHandler.cs b/src/Titanium.Web.Proxy/ExplicitClientHandler.cs index 79fe227b9..3c310894e 100644 --- a/src/Titanium.Web.Proxy/ExplicitClientHandler.cs +++ b/src/Titanium.Web.Proxy/ExplicitClientHandler.cs @@ -123,8 +123,8 @@ private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClientConnect { connectRequest.IsHttps = true; // todo: move this line to the previous "if" - var sslProtocol = clientHelloInfo.SslProtocol; - if ((sslProtocol & SupportedSslProtocols) == SslProtocols.None) + var sslProtocol = clientHelloInfo.SslProtocol & SupportedSslProtocols; + if (sslProtocol == SslProtocols.None) { throw new Exception("Unsupported client SSL version."); } diff --git a/src/Titanium.Web.Proxy/Extensions/SslExtensions.cs b/src/Titanium.Web.Proxy/Extensions/SslExtensions.cs index fc037cd80..5ebac059a 100644 --- a/src/Titanium.Web.Proxy/Extensions/SslExtensions.cs +++ b/src/Titanium.Web.Proxy/Extensions/SslExtensions.cs @@ -3,9 +3,11 @@ using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; +using System.Text; using System.Threading; using System.Threading.Tasks; using Titanium.Web.Proxy.StreamExtended; +using Titanium.Web.Proxy.StreamExtended.Models; namespace Titanium.Web.Proxy.Extensions { @@ -31,24 +33,24 @@ internal static class SslExtensions { if (clientHelloInfo.Extensions != null && clientHelloInfo.Extensions.TryGetValue("ALPN", out var alpnExtension)) { - var alpn = alpnExtension.Data.Split(','); - if (alpn.Length != 0) + var alpn = alpnExtension.Alpns; + if (alpn.Count != 0) { - var result = new List(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 alpn; + } + } + + return null; + } + + internal static List? GetSslProtocols(this ClientHelloInfo clientHelloInfo) + { + if (clientHelloInfo.Extensions != null && clientHelloInfo.Extensions.TryGetValue("supported_versions", out var versions)) + { + var protocols = versions.Protocols; + if (protocols.Count != 0) + { + return protocols; } } @@ -80,10 +82,54 @@ internal static Task AuthenticateAsServerAsync(this SslStream sslStream, SslServ #if !NET6_0_OR_GREATER namespace System.Net.Security { - internal enum SslApplicationProtocol + internal struct SslApplicationProtocol { - Http11, - Http2 + public static readonly SslApplicationProtocol Http11 = new SslApplicationProtocol(SslExtension.Http11Utf8); + + public static readonly SslApplicationProtocol Http2 = new SslApplicationProtocol(SslExtension.Http2Utf8); + + public static readonly SslApplicationProtocol Http3 = new SslApplicationProtocol(SslExtension.Http3Utf8); + + private readonly byte[] readOnlyProtocol; + + public ReadOnlyMemory Protocol => readOnlyProtocol; + + public SslApplicationProtocol(byte[] protocol) + { + readOnlyProtocol = protocol; + } + + public bool Equals(SslApplicationProtocol other) => Protocol.Span.SequenceEqual(other.Protocol.Span); + + public override bool Equals(object? obj) => obj is SslApplicationProtocol protocol && Equals(protocol); + + public override int GetHashCode() + { + var arr = Protocol; + if (arr.Length == 0) + { + return 0; + } + + int hash = 0; + for (int i = 0; i < arr.Length; i++) + { + hash = ((hash << 5) + hash) ^ arr.Span[i]; + } + + return hash; + } + + public override string ToString() + { + return Encoding.UTF8.GetString(readOnlyProtocol); + } + + public static bool operator ==(SslApplicationProtocol left, SslApplicationProtocol right) => + left.Equals(right); + + public static bool operator !=(SslApplicationProtocol left, SslApplicationProtocol right) => + !(left == right); } [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleType", Justification = diff --git a/src/Titanium.Web.Proxy/Extensions/StringExtensions.cs b/src/Titanium.Web.Proxy/Extensions/StringExtensions.cs index 1aaa18d20..cb8a0cb7d 100644 --- a/src/Titanium.Web.Proxy/Extensions/StringExtensions.cs +++ b/src/Titanium.Web.Proxy/Extensions/StringExtensions.cs @@ -1,5 +1,8 @@ using System; +using System.Buffers.Text; +using System.Buffers; using System.Globalization; +using System.Text; namespace Titanium.Web.Proxy.Extensions; @@ -24,4 +27,31 @@ internal static int IndexOfIgnoreCase(this string str, string? value) { return CultureInfo.CurrentCulture.CompareInfo.IndexOf(str, value, CompareOptions.IgnoreCase); } + + internal static unsafe string ByteArrayToHexString(this ReadOnlySpan data) + { + if (data.Length == 0) + { + return string.Empty; + } + + int length = data.Length * 3; + Span buf = stackalloc byte[length]; + var buf2 = buf; + foreach (var b in data) + { + Utf8Formatter.TryFormat(b, buf2, out _, new StandardFormat('X', 2)); + buf2[2] = 32; // space + buf2 = buf2.Slice(3); + } + +#if NET6_0_OR_GREATER + return Encoding.UTF8.GetString(buf.Slice(0, length - 1)); +#else + fixed (byte* bp = buf) + { + return Encoding.UTF8.GetString(bp, length -1); + } +#endif + } } \ No newline at end of file diff --git a/src/Titanium.Web.Proxy/Network/Models/SslExtension.cs b/src/Titanium.Web.Proxy/Network/Models/SslExtension.cs index 457a63cb1..84e9e37b7 100644 --- a/src/Titanium.Web.Proxy/Network/Models/SslExtension.cs +++ b/src/Titanium.Web.Proxy/Network/Models/SslExtension.cs @@ -1,3 +1,12 @@ +using System.Collections.Generic; +using System.Text; +using System; +using System.Linq; +using System.Net.Security; +using System.Security.Authentication; +using Titanium.Web.Proxy.Extensions; +using System.Xml.Linq; + namespace Titanium.Web.Proxy.StreamExtended.Models; /// @@ -5,21 +14,26 @@ namespace Titanium.Web.Proxy.StreamExtended.Models; /// public class SslExtension { + internal static readonly byte[] Http11Utf8 = new byte[] { 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31 }; // "http/1.1" + internal static readonly byte[] Http2Utf8 = new byte[] { 0x68, 0x32 }; // "h2" + internal static readonly byte[] Http3Utf8 = new byte[] { 0x68, 0x33 }; // "h3" + /// /// Initializes a new instance of the class. /// /// The value. - /// The name. /// The data. /// The position. - public SslExtension(int value, string name, string data, int position) + public SslExtension(int value, ReadOnlyMemory data, int position) { Value = value; - Name = name; - Data = data; + this.data = data; + Name = GetExtensionName(value); Position = position; } + private ReadOnlyMemory data; + /// /// Gets the value. /// @@ -42,7 +56,11 @@ public SslExtension(int value, string name, string data, int position) /// /// The data. /// - public string Data { get; } + public string Data => GetExtensionData(Value, data.Span); + + internal List Alpns => GetApplicationLayerProtocolNegotiation(data.Span); + + internal List Protocols => GetSupportedVersions(data.Span); /// /// Gets the position. @@ -51,4 +69,601 @@ public SslExtension(int value, string name, string data, int position) /// The position. /// public int Position { get; } + + private static unsafe string GetExtensionData(int value, ReadOnlySpan data) + { + // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml + switch (value) + { + case 0: + var stringBuilder = new StringBuilder(); + var index = 2; + while (index < data.Length) + { + int nameType = data[index]; + var count = (data[index + 1] << 8) + data[index + 2]; +#if NET6_0_OR_GREATER + var str = Encoding.ASCII.GetString(data.Slice(index + 3, count)); +#else + string str; + fixed (byte* bp = data.Slice(index + 3)) + { + str = Encoding.ASCII.GetString(bp, count); + } +#endif + if (nameType == 0) + { + if (stringBuilder.Length > 0) + { + stringBuilder.Append("; "); + stringBuilder.Append(str); + } + else + { + stringBuilder.Append(str); + } + } + + index += 3 + count; + } + + return stringBuilder.ToString(); + case 5: + if (data.Length == 5 && data[0] == 1 && data[1] == 0 && data[2] == 0 && data[3] == 0 && data[4] == 0) + return "OCSP - Implicit Responder"; + + return data.ByteArrayToHexString(); + case 10: + return GetSupportedGroup(data); + case 11: + return GetEcPointFormats(data); + case 13: + return GetSignatureAlgorithms(data); + case 16: + var protocols = GetApplicationLayerProtocolNegotiation(data); +#if NET6_0_OR_GREATER + return string.Join(", ", protocols.Select(x => Encoding.UTF8.GetString(x.Protocol.Span))); +#else + return string.Join(", ", protocols.Select(x => x.ToString())); +#endif + case 21: + for (int i = 0; i < data.Length; i++) + { + if (data[i] != 0) + { + return data.ByteArrayToHexString(); + } + } + + return $"{data.Length:N0} null bytes"; + case 43: + return string.Join(", ", GetSupportedVersions(data)); + case 50: + return GetSignatureAlgorithms(data); + case 35655: + return $"{data.Length} bytes"; + default: + return data.ByteArrayToHexString(); + } + } + + private static string GetSupportedGroup(ReadOnlySpan data) + { + // https://datatracker.ietf.org/doc/draft-ietf-tls-rfc4492bis/?include_text=1 + var list = new List(); + if (data.Length < 2) return string.Empty; + + var i = 2; + while (i < data.Length - 1) + { + var namedCurve = (data[i] << 8) + data[i + 1]; + switch (namedCurve) + { + case 1: + list.Add("sect163k1 [0x1]"); // deprecated + break; + case 2: + list.Add("sect163r1 [0x2]"); // deprecated + break; + case 3: + list.Add("sect163r2 [0x3]"); // deprecated + break; + case 4: + list.Add("sect193r1 [0x4]"); // deprecated + break; + case 5: + list.Add("sect193r2 [0x5]"); // deprecated + break; + case 6: + list.Add("sect233k1 [0x6]"); // deprecated + break; + case 7: + list.Add("sect233r1 [0x7]"); // deprecated + break; + case 8: + list.Add("sect239k1 [0x8]"); // deprecated + break; + case 9: + list.Add("sect283k1 [0x9]"); // deprecated + break; + case 10: + list.Add("sect283r1 [0xA]"); // deprecated + break; + case 11: + list.Add("sect409k1 [0xB]"); // deprecated + break; + case 12: + list.Add("sect409r1 [0xC]"); // deprecated + break; + case 13: + list.Add("sect571k1 [0xD]"); // deprecated + break; + case 14: + list.Add("sect571r1 [0xE]"); // deprecated + break; + case 15: + list.Add("secp160k1 [0xF]"); // deprecated + break; + case 16: + list.Add("secp160r1 [0x10]"); // deprecated + break; + case 17: + list.Add("secp160r2 [0x11]"); // deprecated + break; + case 18: + list.Add("secp192k1 [0x12]"); // deprecated + break; + case 19: + list.Add("secp192r1 [0x13]"); // deprecated + break; + case 20: + list.Add("secp224k1 [0x14]"); // deprecated + break; + case 21: + list.Add("secp224r1 [0x15]"); // deprecated + break; + case 22: + list.Add("secp256k1 [0x16]"); // deprecated + break; + case 23: + list.Add("secp256r1 [0x17]"); + break; + case 24: + list.Add("secp384r1 [0x18]"); + break; + case 25: + list.Add("secp521r1 [0x19]"); + break; + case 26: + list.Add("brainpoolP256r1 [0x1A]"); + break; + case 27: + list.Add("brainpoolP384r1 [0x1B]"); + break; + case 28: + list.Add("brainpoolP512r1 [0x1C]"); + break; + case 29: + list.Add("x25519 [0x1D]"); + break; + case 30: + list.Add("x448 [0x1E]"); + break; + case 256: + list.Add("ffdhe2048 [0x0100]"); + break; + case 257: + list.Add("ffdhe3072 [0x0101]"); + break; + case 258: + list.Add("ffdhe4096 [0x0102]"); + break; + case 259: + list.Add("ffdhe6144 [0x0103]"); + break; + case 260: + list.Add("ffdhe8192 [0x0104]"); + break; + case 65281: + list.Add("arbitrary_explicit_prime_curves [0xFF01]"); // deprecated + break; + case 65282: + list.Add("arbitrary_explicit_char2_curves [0xFF02]"); // deprecated + break; + default: + list.Add($"unknown [0x{namedCurve:X4}]"); + break; + } + + i += 2; + } + + return string.Join(", ", list.ToArray()); + } + + private static string GetEcPointFormats(ReadOnlySpan data) + { + var list = new List(); + if (data.Length < 1) return string.Empty; + + var i = 1; + while (i < data.Length) + { + switch (data[i]) + { + case 0: + list.Add("uncompressed [0x0]"); + break; + case 1: + list.Add("ansiX962_compressed_prime [0x1]"); + break; + case 2: + list.Add("ansiX962_compressed_char2 [0x2]"); + break; + default: + list.Add($"unknown [0x{data[i]:X2}]"); + break; + } + + i += 2; + } + + return string.Join(", ", list.ToArray()); + } + + private static List GetSupportedVersions(ReadOnlySpan data) + { + var list = new List(); + if (data.Length < 2) + { + return list; + } + + int i = 0; + if (data.Length > 2) + { + // client hello contains a list + i = 1; + } + + for (; i < data.Length; i += 2) + { + int val = (data[i] << 8) | data[i + 1]; + switch (val) + { + case 0x300: + list.Add("Ssl3.0"); + continue; + case 0x301: + list.Add("Tls1.0"); + continue; + case 0x302: + list.Add("Tls1.1"); + continue; + case 0x303: + list.Add("Tls1.2"); + continue; + case 0x304: + list.Add("Tls1.3"); + continue; + } + + string arg = "unknown"; + + if ((val & 0x0A0A) == 0x0A0A && (val >> 8) == (val & 0xFF)) + { + arg = "grease"; + } + else if ((val & 0x7F00) == 32512) + { + arg = "Tls1.3_draft" + (val & 0xFF); + } + + list.Add($"{arg} [0x{val:x}]"); + } + + return list; + } + + private static string GetSignatureAlgorithms(ReadOnlySpan data) + { + // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml + var num = (data[0] << 8) + data[1]; + var sb = new StringBuilder(); + var index = 2; + while (index < num + 2) + { + int val0 = data[index]; + int val1 = data[index + 1]; + int val = (val0 << 8) + val1; + switch (val) + { + /* RSASSA-PKCS1-v1_5 algorithms */ + case 0x401: + sb.Append("rsa_pkcs1_sha256"); + break; + case 0x501: + sb.Append("rsa_pkcs1_sha384"); + break; + case 0x601: + sb.Append("rsa_pkcs1_sha512"); + break; + + /* ECDSA algorithms */ + case 0x403: + sb.Append("ecdsa_secp256r1_sha256"); + break; + case 0x503: + sb.Append("ecdsa_secp384r1_sha384"); + break; + case 0x603: + sb.Append("ecdsa_secp521r1_sha512"); + break; + + /* RSASSA-PSS algorithms with public key OID rsaEncryption */ + case 0x804: + sb.Append("rsa_pss_rsae_sha256"); + break; + case 0x805: + sb.Append("rsa_pss_rsae_sha384"); + break; + case 0x806: + sb.Append("rsa_pss_rsae_sha512"); + break; + + /* EdDSA algorithms */ + case 0x807: + sb.Append("ed25519"); + break; + case 0x808: + sb.Append("ed448"); + break; + + /* RSASSA-PSS algorithms with public key OID RSASSA-PSS */ + case 0x809: + sb.Append("rsa_pss_pss_sha256"); + break; + case 0x80A: + sb.Append("rsa_pss_pss_sha384"); + break; + case 0x80B: + sb.Append("rsa_pss_pss_sha512"); + break; + + /* Legacy algorithms */ + case 0x201: + sb.Append("rsa_pkcs1_sha1"); + break; + case 0x203: + sb.Append("ecdsa_sha1"); + break; + + default: + switch (val1) + { + case 0: + sb.Append("anonymous"); + break; + case 1: + sb.Append("rsa"); + break; + case 2: + sb.Append("dsa"); + break; + case 3: + sb.Append("ecdsa"); + break; + case 7: + sb.Append("ed25519"); + break; + case 8: + sb.Append("ed448"); + break; + case 64: + sb.Append("gostr34102012_256"); + break; + case 65: + sb.Append("gostr34102012_512"); + break; + default: + sb.AppendFormat(val1 >= 224 ? "Reserved for Private Use[0x{0:X2}]" : "Reserved[0x{0:X2}]", + val1); + break; + } + + sb.AppendFormat("_"); + + switch (val0) + { + case 0: + sb.Append("none"); + break; + case 1: + sb.Append("md5"); + break; + case 2: + sb.Append("sha1"); + break; + case 3: + sb.Append("sha224"); + break; + case 4: + sb.Append("sha256"); + break; + case 5: + sb.Append("sha384"); + break; + case 6: + sb.Append("sha512"); + break; + case 8: + sb.Append("Intrinsic"); + break; + default: + sb.AppendFormat(val0 >= 224 ? "Reserved for Private Use[0x{0:X2}]" : "Reserved[0x{0:X2}]", + val0); + break; + } + + break; + } + + sb.AppendFormat(", "); + index += 2; + } + + if (sb.Length > 1) + sb.Length -= 2; + + return sb.ToString(); + } + + private static List GetApplicationLayerProtocolNegotiation(ReadOnlySpan data) + { + var list = new List(); + var index = 2; + while (index < data.Length) + { + int count = data[index]; + var protocol = data.Slice(index + 1, count); + if (Http11Utf8.AsSpan().SequenceEqual(protocol)) + { + list.Add(SslApplicationProtocol.Http11); + } + else if (Http2Utf8.AsSpan().SequenceEqual(protocol)) + { + list.Add(SslApplicationProtocol.Http2); + } + else if (Http3Utf8.AsSpan().SequenceEqual(protocol)) + { + list.Add(SslApplicationProtocol.Http3); + } + else + { + list.Add(new SslApplicationProtocol(protocol.ToArray())); + } + + index += 1 + count; + } + + return list; + } + + private static string GetExtensionName(int value) + { + // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml + switch (value) + { + case 0: + return "server_name"; + case 1: + return "max_fragment_length"; + case 2: + return "client_certificate_url"; + case 3: + return "trusted_ca_keys"; + case 4: + return "truncated_hmac"; + case 5: + return "status_request"; + case 6: + return "user_mapping"; + case 7: + return "client_authz"; + case 8: + return "server_authz"; + case 9: + return "cert_type"; + case 10: + return "supported_groups"; // renamed from "elliptic_curves" (RFC 7919 / TLS 1.3) + case 11: + return "ec_point_formats"; + case 12: + return "srp"; + case 13: + return "signature_algorithms"; + case 14: + return "use_srtp"; + case 15: + return "heartbeat"; + case 16: + return "ALPN"; // application_layer_protocol_negotiation + case 17: + return "status_request_v2"; + case 18: + return "signed_certificate_timestamp"; + case 19: + return "client_certificate_type"; + case 20: + return "server_certificate_type"; + case 21: + return "padding"; + case 22: + return "encrypt_then_mac"; + case 23: + return "extended_master_secret"; + case 24: + return + "token_binding"; // TEMPORARY - registered 2016-02-04, extension registered 2017-01-12, expires 2018-02-04 + case 25: + return "cached_info"; + case 26: + return "quic_transports_parameters"; // Not yet assigned by IANA (QUIC-TLS Draft04) + case 35: + return "SessionTicket TLS"; + // TLS 1.3 draft: https://tools.ietf.org/html/draft-ietf-tls-tls13 + case 40: + return "key_share"; + case 41: + return "pre_shared_key"; + case 42: + return "early_data"; + case 43: + return "supported_versions"; + case 44: + return "cookie"; + case 45: + return "psk_key_exchange_modes"; + case 46: + return "ticket_early_data_info"; + case 47: + return "certificate_authorities"; + case 48: + return "oid_filters"; + case 49: + return "post_handshake_auth"; + case 2570: // 0a0a + case 6682: // 1a1a + case 10794: // 2a2a + case 14906: // 3a3a + case 19018: // 4a4a + case 23130: // 5a5a + case 27242: // 6a6a + case 31354: // 7a7a + case 35466: // 8a8a + case 39578: // 9a9a + case 43690: // aaaa + case 47802: // baba + case 51914: // caca + case 56026: // dada + case 60138: // eaea + case 64250: // fafa + return "Reserved (GREASE)"; + case 13172: + return "next_protocol_negotiation"; + case 30031: + return "channel_id_old"; // Google + case 30032: + return "channel_id"; // Google + case 35655: + return "draft-agl-tls-padding"; + case 65281: + return "renegotiation_info"; + case 65282: + return + "Draft version of TLS 1.3"; // for experimentation only https://www.ietf.org/mail-archive/web/tls/current/msg20853.html + default: + return $"unknown_{value:x2}"; + } + } } \ No newline at end of file diff --git a/src/Titanium.Web.Proxy/Network/Readers/PeekStreamReader.cs b/src/Titanium.Web.Proxy/Network/Readers/PeekStreamReader.cs index 4c552a687..4fb86cf67 100644 --- a/src/Titanium.Web.Proxy/Network/Readers/PeekStreamReader.cs +++ b/src/Titanium.Web.Proxy/Network/Readers/PeekStreamReader.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System; +using System.Threading; using System.Threading.Tasks; namespace Titanium.Web.Proxy.StreamExtended.Network; @@ -48,4 +49,9 @@ public byte[] ReadBytes(int length) return buffer; } + + public void ReadBytes(Span data) + { + for (var i = 0; i < data.Length; i++) data[i] = ReadByte(); + } } \ No newline at end of file diff --git a/src/Titanium.Web.Proxy/Network/Ssl/ClientHelloInfo.cs b/src/Titanium.Web.Proxy/Network/Ssl/ClientHelloInfo.cs index 680da1a17..7353f3178 100644 --- a/src/Titanium.Web.Proxy/Network/Ssl/ClientHelloInfo.cs +++ b/src/Titanium.Web.Proxy/Network/Ssl/ClientHelloInfo.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Security.Authentication; using System.Text; +using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.StreamExtended.Models; namespace Titanium.Web.Proxy.StreamExtended; @@ -71,7 +73,20 @@ public SslProtocols SslProtocol var major = MajorVersion; var minor = MinorVersion; if (major == 3 && minor == 3) + { +#if NET6_0_OR_GREATER + var protocols = this.GetSslProtocols(); + if (protocols != null) + { + if (protocols.Contains("Tls1.3")) + { + return SslProtocols.Tls12 | SslProtocols.Tls13; + } + } +#endif + return SslProtocols.Tls12; + } if (major == 3 && minor == 2) return SslProtocols.Tls11; @@ -121,9 +136,9 @@ public override string ToString() $"A SSLv{HandshakeVersion}-compatible ClientHello handshake was found. Titanium extracted the parameters below."); sb.AppendLine(); sb.AppendLine($"Version: {SslVersionToString(MajorVersion, MinorVersion)}"); - sb.AppendLine($"Random: {string.Join(" ", Random.Select(x => x.ToString("X2")))}"); + sb.AppendLine($"Random: {StringExtensions.ByteArrayToHexString(Random)}"); sb.AppendLine($"\"Time\": {Time}"); - sb.AppendLine($"SessionID: {string.Join(" ", SessionId.Select(x => x.ToString("X2")))}"); + sb.AppendLine($"SessionID: {StringExtensions.ByteArrayToHexString(SessionId)}"); if (Extensions != null) { diff --git a/src/Titanium.Web.Proxy/Network/Ssl/ServerHelloInfo.cs b/src/Titanium.Web.Proxy/Network/Ssl/ServerHelloInfo.cs index c0b1a44ea..5c614e5f8 100644 --- a/src/Titanium.Web.Proxy/Network/Ssl/ServerHelloInfo.cs +++ b/src/Titanium.Web.Proxy/Network/Ssl/ServerHelloInfo.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.StreamExtended.Models; namespace Titanium.Web.Proxy.StreamExtended; @@ -93,9 +94,9 @@ public override string ToString() $"A SSLv{HandshakeVersion}-compatible ServerHello handshake was found. Titanium extracted the parameters below."); sb.AppendLine(); sb.AppendLine($"Version: {SslVersionToString(MajorVersion, MinorVersion)}"); - sb.AppendLine($"Random: {string.Join(" ", Random.Select(x => x.ToString("X2")))}"); + sb.AppendLine($"Random: {StringExtensions.ByteArrayToHexString(Random)}"); sb.AppendLine($"\"Time\": {Time}"); - sb.AppendLine($"SessionID: {string.Join(" ", SessionId.Select(x => x.ToString("X2")))}"); + sb.AppendLine($"SessionID: {StringExtensions.ByteArrayToHexString(SessionId)}"); if (Extensions != null) { diff --git a/src/Titanium.Web.Proxy/Network/Ssl/SslExtensions.cs b/src/Titanium.Web.Proxy/Network/Ssl/SslExtensions.cs deleted file mode 100644 index ae93e1f94..000000000 --- a/src/Titanium.Web.Proxy/Network/Ssl/SslExtensions.cs +++ /dev/null @@ -1,432 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Titanium.Web.Proxy.StreamExtended.Models; - -namespace Titanium.Web.Proxy.StreamExtended; - -internal class SslExtensions -{ - internal static SslExtension GetExtension(int value, byte[] data, int position) - { - var name = GetExtensionName(value); - var dataStr = GetExtensionData(value, data); - return new SslExtension(value, name, dataStr, position); - } - - private static string GetExtensionData(int value, byte[] data) - { - // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml - switch (value) - { - case 0: - var stringBuilder = new StringBuilder(); - var index = 2; - while (index < data.Length) - { - int nameType = data[index]; - var count = (data[index + 1] << 8) + data[index + 2]; - var str = Encoding.ASCII.GetString(data, index + 3, count); - if (nameType == 0) - stringBuilder.AppendFormat("{0}{1}", stringBuilder.Length > 1 ? "; " : string.Empty, str); - - index += 3 + count; - } - - return stringBuilder.ToString(); - case 5: - if (data.Length == 5 && data[0] == 1 && data[1] == 0 && data[2] == 0 && data[3] == 0 && data[4] == 0) - return "OCSP - Implicit Responder"; - - return ByteArrayToString(data); - case 10: - return GetSupportedGroup(data); - case 11: - return GetEcPointFormats(data); - case 13: - return GetSignatureAlgorithms(data); - case 16: - return GetApplicationLayerProtocolNegotiation(data); - case 35655: - return $"{data.Length} bytes"; - default: - return ByteArrayToString(data); - } - } - - private static string GetSupportedGroup(byte[] data) - { - // https://datatracker.ietf.org/doc/draft-ietf-tls-rfc4492bis/?include_text=1 - var list = new List(); - if (data.Length < 2) return string.Empty; - - var i = 2; - while (i < data.Length - 1) - { - var namedCurve = (data[i] << 8) + data[i + 1]; - switch (namedCurve) - { - case 1: - list.Add("sect163k1 [0x1]"); // deprecated - break; - case 2: - list.Add("sect163r1 [0x2]"); // deprecated - break; - case 3: - list.Add("sect163r2 [0x3]"); // deprecated - break; - case 4: - list.Add("sect193r1 [0x4]"); // deprecated - break; - case 5: - list.Add("sect193r2 [0x5]"); // deprecated - break; - case 6: - list.Add("sect233k1 [0x6]"); // deprecated - break; - case 7: - list.Add("sect233r1 [0x7]"); // deprecated - break; - case 8: - list.Add("sect239k1 [0x8]"); // deprecated - break; - case 9: - list.Add("sect283k1 [0x9]"); // deprecated - break; - case 10: - list.Add("sect283r1 [0xA]"); // deprecated - break; - case 11: - list.Add("sect409k1 [0xB]"); // deprecated - break; - case 12: - list.Add("sect409r1 [0xC]"); // deprecated - break; - case 13: - list.Add("sect571k1 [0xD]"); // deprecated - break; - case 14: - list.Add("sect571r1 [0xE]"); // deprecated - break; - case 15: - list.Add("secp160k1 [0xF]"); // deprecated - break; - case 16: - list.Add("secp160r1 [0x10]"); // deprecated - break; - case 17: - list.Add("secp160r2 [0x11]"); // deprecated - break; - case 18: - list.Add("secp192k1 [0x12]"); // deprecated - break; - case 19: - list.Add("secp192r1 [0x13]"); // deprecated - break; - case 20: - list.Add("secp224k1 [0x14]"); // deprecated - break; - case 21: - list.Add("secp224r1 [0x15]"); // deprecated - break; - case 22: - list.Add("secp256k1 [0x16]"); // deprecated - break; - case 23: - list.Add("secp256r1 [0x17]"); - break; - case 24: - list.Add("secp384r1 [0x18]"); - break; - case 25: - list.Add("secp521r1 [0x19]"); - break; - case 26: - list.Add("brainpoolP256r1 [0x1A]"); - break; - case 27: - list.Add("brainpoolP384r1 [0x1B]"); - break; - case 28: - list.Add("brainpoolP512r1 [0x1C]"); - break; - case 29: - list.Add("x25519 [0x1D]"); - break; - case 30: - list.Add("x448 [0x1E]"); - break; - case 256: - list.Add("ffdhe2048 [0x0100]"); - break; - case 257: - list.Add("ffdhe3072 [0x0101]"); - break; - case 258: - list.Add("ffdhe4096 [0x0102]"); - break; - case 259: - list.Add("ffdhe6144 [0x0103]"); - break; - case 260: - list.Add("ffdhe8192 [0x0104]"); - break; - case 65281: - list.Add("arbitrary_explicit_prime_curves [0xFF01]"); // deprecated - break; - case 65282: - list.Add("arbitrary_explicit_char2_curves [0xFF02]"); // deprecated - break; - default: - list.Add($"unknown [0x{namedCurve:X4}]"); - break; - } - - i += 2; - } - - return string.Join(", ", list.ToArray()); - } - - private static string GetEcPointFormats(byte[] data) - { - var list = new List(); - if (data.Length < 1) return string.Empty; - - var i = 1; - while (i < data.Length) - { - switch (data[i]) - { - case 0: - list.Add("uncompressed [0x0]"); - break; - case 1: - list.Add("ansiX962_compressed_prime [0x1]"); - break; - case 2: - list.Add("ansiX962_compressed_char2 [0x2]"); - break; - default: - list.Add($"unknown [0x{data[i]:X2}]"); - break; - } - - i += 2; - } - - return string.Join(", ", list.ToArray()); - } - - private static string GetSignatureAlgorithms(byte[] data) - { - // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml - var num = (data[0] << 8) + data[1]; - var sb = new StringBuilder(); - var index = 2; - while (index < num + 2) - { - switch (data[index]) - { - case 0: - sb.Append("none"); - break; - case 1: - sb.Append("md5"); - break; - case 2: - sb.Append("sha1"); - break; - case 3: - sb.Append("sha224"); - break; - case 4: - sb.Append("sha256"); - break; - case 5: - sb.Append("sha384"); - break; - case 6: - sb.Append("sha512"); - break; - case 8: - sb.Append("Intrinsic"); - break; - default: - sb.AppendFormat("Unknown[0x{0:X2}]", data[index]); - break; - } - - sb.AppendFormat("_"); - switch (data[index + 1]) - { - case 0: - sb.Append("anonymous"); - break; - case 1: - sb.Append("rsa"); - break; - case 2: - sb.Append("dsa"); - break; - case 3: - sb.Append("ecdsa"); - break; - case 7: - sb.Append("ed25519"); - break; - case 8: - sb.Append("ed448"); - break; - default: - sb.AppendFormat("Unknown[0x{0:X2}]", data[index + 1]); - break; - } - - sb.AppendFormat(", "); - index += 2; - } - - if (sb.Length > 1) - sb.Length -= 2; - - return sb.ToString(); - } - - private static string GetApplicationLayerProtocolNegotiation(byte[] data) - { - var stringList = new List(); - var index = 2; - while (index < data.Length) - { - int count = data[index]; - stringList.Add(Encoding.ASCII.GetString(data, index + 1, count)); - index += 1 + count; - } - - return string.Join(", ", stringList.ToArray()); - } - - private static string GetExtensionName(int value) - { - // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml - switch (value) - { - case 0: - return "server_name"; - case 1: - return "max_fragment_length"; - case 2: - return "client_certificate_url"; - case 3: - return "trusted_ca_keys"; - case 4: - return "truncated_hmac"; - case 5: - return "status_request"; - case 6: - return "user_mapping"; - case 7: - return "client_authz"; - case 8: - return "server_authz"; - case 9: - return "cert_type"; - case 10: - return "supported_groups"; // renamed from "elliptic_curves" (RFC 7919 / TLS 1.3) - case 11: - return "ec_point_formats"; - case 12: - return "srp"; - case 13: - return "signature_algorithms"; - case 14: - return "use_srtp"; - case 15: - return "heartbeat"; - case 16: - return "ALPN"; // application_layer_protocol_negotiation - case 17: - return "status_request_v2"; - case 18: - return "signed_certificate_timestamp"; - case 19: - return "client_certificate_type"; - case 20: - return "server_certificate_type"; - case 21: - return "padding"; - case 22: - return "encrypt_then_mac"; - case 23: - return "extended_master_secret"; - case 24: - return - "token_binding"; // TEMPORARY - registered 2016-02-04, extension registered 2017-01-12, expires 2018-02-04 - case 25: - return "cached_info"; - case 26: - return "quic_transports_parameters"; // Not yet assigned by IANA (QUIC-TLS Draft04) - case 35: - return "SessionTicket TLS"; - // TLS 1.3 draft: https://tools.ietf.org/html/draft-ietf-tls-tls13 - case 40: - return "key_share"; - case 41: - return "pre_shared_key"; - case 42: - return "early_data"; - case 43: - return "supported_versions"; - case 44: - return "cookie"; - case 45: - return "psk_key_exchange_modes"; - case 46: - return "ticket_early_data_info"; - case 47: - return "certificate_authorities"; - case 48: - return "oid_filters"; - case 49: - return "post_handshake_auth"; - case 2570: // 0a0a - case 6682: // 1a1a - case 10794: // 2a2a - case 14906: // 3a3a - case 19018: // 4a4a - case 23130: // 5a5a - case 27242: // 6a6a - case 31354: // 7a7a - case 35466: // 8a8a - case 39578: // 9a9a - case 43690: // aaaa - case 47802: // baba - case 51914: // caca - case 56026: // dada - case 60138: // eaea - case 64250: // fafa - return "Reserved (GREASE)"; - case 13172: - return "next_protocol_negotiation"; - case 30031: - return "channel_id_old"; // Google - case 30032: - return "channel_id"; // Google - case 35655: - return "draft-agl-tls-padding"; - case 65281: - return "renegotiation_info"; - case 65282: - return - "Draft version of TLS 1.3"; // for experimentation only https://www.ietf.org/mail-archive/web/tls/current/msg20853.html - default: - return $"unknown_{value:x2}"; - } - } - - private static string ByteArrayToString(byte[] data) - { - return string.Join(" ", data.Select(x => x.ToString("X2"))); - } -} \ No newline at end of file diff --git a/src/Titanium.Web.Proxy/Network/Ssl/SslTools.cs b/src/Titanium.Web.Proxy/Network/Ssl/SslTools.cs index f97df4f8f..1b419d588 100644 --- a/src/Titanium.Web.Proxy/Network/Ssl/SslTools.cs +++ b/src/Titanium.Web.Proxy/Network/Ssl/SslTools.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Buffers.Binary; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Titanium.Web.Proxy.StreamExtended.BufferPool; @@ -267,16 +269,16 @@ public static async Task IsServerHello(IPeekStream stream, IBufferPool buf if (await peekStreamReader.EnsureBufferLength(extensionsLength, cancellationToken)) { + var extensionsData = peekStreamReader.ReadBytes(extensionsLength).AsMemory(); extensions = new Dictionary(); var idx = 0; - while (extensionsLength > 3) + while (extensionsData.Length > 3) { - var id = peekStreamReader.ReadInt16(); - var length = peekStreamReader.ReadInt16(); - var data = peekStreamReader.ReadBytes(length); - var extension = SslExtensions.GetExtension(id, data, idx++); + var id = BinaryPrimitives.ReadInt16BigEndian(extensionsData.Span); + var length = BinaryPrimitives.ReadInt16BigEndian(extensionsData.Span.Slice(2)); + var extension = new SslExtension(id, extensionsData.Slice(4, length), idx++); extensions[extension.Name] = extension; - extensionsLength -= 4 + length; + extensionsData = extensionsData.Slice(4 + length); } } } diff --git a/src/Titanium.Web.Proxy/ProxyServer.cs b/src/Titanium.Web.Proxy/ProxyServer.cs index e5e74c152..4fa65f853 100644 --- a/src/Titanium.Web.Proxy/ProxyServer.cs +++ b/src/Titanium.Web.Proxy/ProxyServer.cs @@ -246,7 +246,11 @@ public ProxyServer(string? rootCertificateName, string? rootCertificateIssuerNam /// #pragma warning disable 618 public SslProtocols SupportedSslProtocols { get; set; } = - SslProtocols.Ssl3 | SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; + SslProtocols.Ssl3 | SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 +#if NET6_0_OR_GREATER + | SslProtocols.Tls13 +#endif + ; #pragma warning restore 618 /// diff --git a/src/Titanium.Web.Proxy/TransparentClientHandler.cs b/src/Titanium.Web.Proxy/TransparentClientHandler.cs index 6c4f8a584..735844e55 100644 --- a/src/Titanium.Web.Proxy/TransparentClientHandler.cs +++ b/src/Titanium.Web.Proxy/TransparentClientHandler.cs @@ -57,8 +57,8 @@ private async Task HandleClient(TransparentBaseProxyEndPoint endPoint, TcpClient if (endPoint.DecryptSsl && args.DecryptSsl) { - var sslProtocol = clientHelloInfo.SslProtocol; - if ((sslProtocol & SupportedSslProtocols) == SslProtocols.None) + var sslProtocol = clientHelloInfo.SslProtocol & SupportedSslProtocols; + if (sslProtocol == SslProtocols.None) { throw new Exception("Unsupported client SSL version."); }