Program.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. using System;
  2. using System.IO;
  3. using System.IO.Compression;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Net.Security;
  7. using System.Net.Sockets;
  8. using System.Runtime.ConstrainedExecution;
  9. using System.Security.Cryptography;
  10. using System.Security.Cryptography.X509Certificates;
  11. using System.Text;
  12. using System.Threading;
  13. using DnsClient;
  14. //////////////////////////////////
  15. #region __________ INIT __________
  16. using Microsoft.Extensions.Logging.Console;
  17. var builder = WebApplication.CreateBuilder(args);
  18. //控制台日志格式
  19. builder.Services.AddLogging(opt =>
  20. {
  21. opt.AddSimpleConsole(p =>
  22. {
  23. p.TimestampFormat = "[dd HH:mm:ss] ";
  24. p.SingleLine = true;
  25. p.ColorBehavior = LoggerColorBehavior.Enabled;
  26. });
  27. });
  28. using var host = builder.Build();
  29. builder.WebHost.UseUrls("http://*:0");
  30. await host.StartAsync();
  31. var isRunning = true;
  32. var cts = new CancellationTokenSource();
  33. Console.CancelKeyPress += (_, _) =>
  34. {
  35. isRunning = false;
  36. cts.Cancel(false);
  37. };
  38. var logger = host.Services.GetRequiredService<ILogger<Program>>();
  39. logger.LogInformation("Hello, World!");
  40. //Main
  41. try
  42. {
  43. await RealMain();
  44. }
  45. catch (Exception ex)
  46. {
  47. logger.LogError(ex, "Main");
  48. }
  49. finally
  50. {
  51. logger.LogInformation("Bye!");
  52. await host.StopAsync();
  53. Console.WriteLine();
  54. Console.Write("Press ENTER to exit...");
  55. Console.ReadLine();
  56. }
  57. #endregion __________ INIT __________
  58. //////////////////////////////////
  59. async Task RealMain()
  60. {
  61. const string url = "http://example.com/index.html";
  62. const string host = "example.com";
  63. const string path = "/index.html";
  64. const string dnsServerName = "reliable-dns-server-in-hosts";
  65. var dnsServerIp = Dns.GetHostEntry(dnsServerName).AddressList.FirstOrDefault();
  66. var lookup = new LookupClient(dnsServerIp);
  67. var result = await lookup.QueryAsync(host, QueryType.A);
  68. var record = result.Answers.ARecords().FirstOrDefault();
  69. var ip = record?.Address;
  70. var tcpClient = new TcpClient();
  71. tcpClient.Connect(new IPEndPoint(ip, 443)); // stuck if was ip gfw-ed
  72. var ssl = new SslStream(tcpClient.GetStream());
  73. var sslOptions = new SslClientAuthenticationOptions
  74. {
  75. TargetHost = string.Empty, // Leave this empty to avoid sending SNI
  76. RemoteCertificateValidationCallback = (_, certificate, chain, errs) =>
  77. {
  78. if (errs == SslPolicyErrors.None) return true;
  79. if (errs != SslPolicyErrors.RemoteCertificateNameMismatch) return false;
  80. if (certificate is not X509Certificate2 cert2) return false;
  81. // 先验证证书的有效期
  82. if (DateTime.Now < cert2.NotBefore || DateTime.Now > cert2.NotAfter) return false;
  83. // 然后比较证书名称和主机名称 (任意一个匹配)
  84. var names = GetAllSubjectAlternativeNames(cert2);
  85. var flagNameMatched = false;
  86. foreach (var certName in names)
  87. {
  88. if (certName.StartsWith("*."))
  89. {
  90. if (!host.EndsWith(certName[2..], StringComparison.OrdinalIgnoreCase)) continue;
  91. flagNameMatched = true;
  92. break;
  93. }
  94. if (certName.Equals(host, StringComparison.OrdinalIgnoreCase))
  95. {
  96. flagNameMatched = true;
  97. break;
  98. }
  99. }
  100. if (flagNameMatched == false) return false;
  101. // 最后检查信任链
  102. if (chain == null) return false;
  103. chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; //检测吊销耗时太长,忽略
  104. chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
  105. chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 10);
  106. chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
  107. var isValidChain = chain.Build(cert2);
  108. if (isValidChain) return true;
  109. foreach (X509ChainStatus chainStatus in chain.ChainStatus)
  110. {
  111. // 仅处理会影响安全性的错误状态
  112. if (chainStatus.Status == X509ChainStatusFlags.RevocationStatusUnknown ||
  113. chainStatus.Status == X509ChainStatusFlags.OfflineRevocation ||
  114. chainStatus.Status == X509ChainStatusFlags.NoError)
  115. {
  116. continue;
  117. }
  118. // 其他任何错误状态都认为证书无效
  119. return false;
  120. }
  121. return true;
  122. }
  123. };
  124. await ssl.AuthenticateAsClientAsync(sslOptions, cts.Token);
  125. ssl.Write(Encoding.ASCII.GetBytes($"GET {path} HTTP/1.1\r\n"));
  126. ssl.Write(Encoding.ASCII.GetBytes($"Host: {host}\r\n"));
  127. ssl.Write(Encoding.ASCII.GetBytes($"\r\n"));
  128. var reader = new StreamReader(ssl);
  129. var lines = new List<string>(); // <- HTTP/1.1 200 OK -- PoC SUCC!
  130. do
  131. {
  132. var line = reader.ReadLine();
  133. if (line == "") break;
  134. lines.Add(line);
  135. } while (true);
  136. int bp = 0;
  137. }
  138. IReadOnlyList<string> GetAllSubjectAlternativeNames(X509Certificate2 cert)
  139. {
  140. var names = new HashSet<string>();
  141. foreach (var extension in cert.Extensions)
  142. {
  143. if (extension is X509SubjectAlternativeNameExtension sanExtension)
  144. {
  145. foreach (var name in sanExtension.EnumerateDnsNames()) names.Add(name);
  146. }
  147. else if (extension.Oid?.Value == "2.5.29.17") // Subject Alternative Name OID
  148. {
  149. var asnData = new AsnEncodedData(extension.Oid, extension.RawData);
  150. var sanString = asnData.Format(true);
  151. var sanParts = sanString.Split(new[] { ", ", "DNS Name=", " " }, StringSplitOptions.RemoveEmptyEntries);
  152. foreach (var part in sanParts)
  153. {
  154. if (!string.IsNullOrEmpty(part) && !part.StartsWith("IPAddress") && !part.StartsWith("Uri"))
  155. {
  156. names.Add(part);
  157. }
  158. }
  159. }
  160. }
  161. // 添加证书的CN(通用名称)
  162. var certName = cert.GetNameInfo(X509NameType.DnsName, false);
  163. if (!string.IsNullOrEmpty(certName)) names.Add(certName);
  164. return [.. names];
  165. }