123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- 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<NoSniProxyHandler>();
- });
- });
- await using var host = builder.Build();
- var logger = host.Services.GetRequiredService<ILogger<Program>>();
- 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<string?> 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<byte> buffer, out string? line)
- {
- var reader = new SequenceReader<byte>(buffer);
- if (reader.TryReadTo(out ReadOnlySpan<byte> 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<string> GetAllSubjectAlternativeNames(X509Certificate2 cert)
- {
- var names = new HashSet<string>();
- 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];
- }
- }
|