using System.Buffers; using System.IO.Pipelines; using Microsoft.AspNetCore.Connections; using System.Net; using System.Security.Cryptography.X509Certificates; using DnsClient; using System.Text; #region INIT using Microsoft.Extensions.Logging.Console; using System.Net.Security; using System.Net.Sockets; using System.Security.Cryptography; var builder = WebApplication.CreateBuilder(args); //控制台日志格式 builder.Services.AddLogging(opt => { opt.AddSimpleConsole(p => { p.TimestampFormat = "[dd HH:mm:ss] "; p.SingleLine = true; p.ColorBehavior = LoggerColorBehavior.Enabled; }); }); builder.WebHost.UseKestrel(opt => { opt.Listen(IPAddress.Any, 0, lisOpt => { lisOpt.UseConnectionHandler(); }); }); await using var host = builder.Build(); var logger = host.Services.GetRequiredService>(); logger.LogInformation("Hello, World!"); await host.RunAsync(); #endregion INIT public class NoSniProxyHandler : ConnectionHandler { private const string dnsServerName = "reliable-dns-server-in-hosts"; private static readonly IPAddress? dnsServerIp = Dns.GetHostEntry(dnsServerName).AddressList.FirstOrDefault(); private static readonly LookupClient lookup = new(dnsServerIp); public override async Task OnConnectedAsync(ConnectionContext connection) { var requestStream = connection.Transport.Input; var responseStream = connection.Transport.Output; var firstLine = await ReadLineAsync(requestStream); if (firstLine == null) { connection.Abort(new ConnectionAbortedException("Canceled: First line rad fail")); return; } var firstLineParts = firstLine.Split(' ', 3); if (firstLineParts.Length < 3) { connection.Abort(new ConnectionAbortedException("Canceled: First line bad")); return; } var method = firstLineParts[0]; var url = firstLineParts[1]; var ver = firstLineParts[2]; var uri = new Uri(url); var targetHost = uri.Host; var result = await lookup.QueryAsync(targetHost, QueryType.A); var record = result.Answers.ARecords().FirstOrDefault(); var ip = record?.Address; var tcpClient = new TcpClient(); await tcpClient.ConnectAsync(new IPEndPoint(ip, 443)); var ssl = new SslStream(tcpClient.GetStream()); var sslOptions = new SslClientAuthenticationOptions { TargetHost = string.Empty, // Leave this empty to avoid sending SNI RemoteCertificateValidationCallback = (o, certificate, chain, errors) => VerifyServerCert(targetHost, certificate, chain, errors), }; await ssl.AuthenticateAsClientAsync(sslOptions, new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token); ssl.Write(Encoding.ASCII.GetBytes($"{method} {uri.PathAndQuery} {ver}\r\n")); var outgoing = requestStream.CopyToAsync(ssl); var inbound = ssl.CopyToAsync(responseStream); await Task.WhenAll(outgoing, inbound); } private async Task ReadLineAsync(PipeReader requestStream) { while (true) { var readResult = await requestStream.ReadAsync(); if (readResult.IsCanceled) return null; var (seq, exm) = ExtractLine(readResult.Buffer, out var line); requestStream.AdvanceTo(seq, exm); if (line != null) return line; } } private (SequencePosition pos, SequencePosition exm) ExtractLine(ReadOnlySequence buffer, out string? line) { var reader = new SequenceReader(buffer); if (reader.TryReadTo(out ReadOnlySpan span, "\r\n"u8)) { if (span.Length > 4096) throw new InvalidDataException("Too long for line"); line = Encoding.ASCII.GetString(span); return (reader.Position, reader.Position); } line = null; return (buffer.Start, buffer.End); } private bool VerifyServerCert(string targetHost, X509Certificate certificate, X509Chain? chain, SslPolicyErrors errs) { if (errs == SslPolicyErrors.None) return true; if (errs != SslPolicyErrors.RemoteCertificateNameMismatch) return false; if (certificate is not X509Certificate2 cert2) return false; // 验证证书的有效期 if (DateTime.Now < cert2.NotBefore || DateTime.Now > cert2.NotAfter) return false; // 然后比较证书名称和主机名称 (任意一个匹配) var names = GetAllSubjectAlternativeNames(cert2); var flagNameMatched = false; foreach (var certName in names) { if (certName.StartsWith("*.")) { if (!targetHost.EndsWith(certName[2..], StringComparison.OrdinalIgnoreCase)) continue; flagNameMatched = true; break; } if (certName.Equals(targetHost, StringComparison.OrdinalIgnoreCase)) { flagNameMatched = true; break; } } if (flagNameMatched == false) return false; // 构建证书链 if (chain == null) return false; chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; // 不检查吊销,太耗时了 chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain; chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 10); chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; var isValidChain = chain.Build(cert2); if (isValidChain) return true; foreach (X509ChainStatus chainStatus in chain.ChainStatus) { // 仅处理会影响安全性的错误状态 if (chainStatus.Status == X509ChainStatusFlags.RevocationStatusUnknown || chainStatus.Status == X509ChainStatusFlags.OfflineRevocation || chainStatus.Status == X509ChainStatusFlags.NoError) { continue; } // 其他任何错误状态都认为证书无效 return false; } return true; } IReadOnlyList GetAllSubjectAlternativeNames(X509Certificate2 cert) { var names = new HashSet(); foreach (var extension in cert.Extensions) { if (extension is X509SubjectAlternativeNameExtension sanExtension) { foreach (var name in sanExtension.EnumerateDnsNames()) names.Add(name); } else if (extension.Oid?.Value == "2.5.29.17") // Subject Alternative Name OID { var asnData = new AsnEncodedData(extension.Oid, extension.RawData); var sanString = asnData.Format(true); var sanParts = sanString.Split(new[] { ", ", "DNS Name=", " " }, StringSplitOptions.RemoveEmptyEntries); foreach (var part in sanParts) { if (!string.IsNullOrEmpty(part) && !part.StartsWith("IPAddress") && !part.StartsWith("Uri")) { names.Add(part); } } } } // 添加证书的CN(通用名称) var certName = cert.GetNameInfo(X509NameType.DnsName, false); if (!string.IsNullOrEmpty(certName)) names.Add(certName); return [.. names]; } }