using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading; namespace ln.http { public class HttpsListener : IDisposable { public static int DefaultPort = 443; public string CertificateStore = Path.Combine(AppContext.BaseDirectory, "certs"); private IPEndPoint _localEndPoint; private Socket _socket; private HTTPServer _httpServer; private X509Certificate _defaultCertificate; private Dictionary _certificateCache = new Dictionary(); public HttpsListener(HTTPServer httpServer) : this(httpServer, DefaultPort) { } public HttpsListener(HTTPServer httpServer, int port) { _httpServer = httpServer; _localEndPoint = new IPEndPoint(IPAddress.IPv6Any, port); Initialize(); } private void Initialize() { _socket = new Socket(SocketType.Stream, ProtocolType.Tcp); _socket.ExclusiveAddressUse = false; _socket.Bind(_localEndPoint); _socket.Listen(); if (File.Exists("localhost.crt")) _defaultCertificate = X509Certificate.CreateFromCertFile("localhost.crt"); else _defaultCertificate = buildSelfSignedServerCertificate(); ThreadPool.QueueUserWorkItem((state )=> ListenerThread()); } private void ListenerThread() { while (_socket?.IsBound ?? false) { try { Socket clientSocket = _socket.Accept(); InitializeTLS(clientSocket); } catch { throw; } } } private void InitializeTLS(Socket clientSocket) { SslStream sslStream = new SslStream(new NetworkStream(clientSocket), false, null, CertificateSelectionCallback); sslStream.AuthenticateAsServer(_defaultCertificate, false, false); _httpServer.Connection( new HttpConnection( _localEndPoint, (IPEndPoint)clientSocket.RemoteEndPoint, sslStream ) ); } private X509Certificate CertificateSelectionCallback(object sender, string targethost, X509CertificateCollection localcertificates, X509Certificate? remotecertificate, string[] acceptableissuers) { Console.Error.WriteLine("Certificate Selection for: {0}", targethost); if (_certificateCache.TryGetValue(targethost, out X509Certificate localCertificate)) { return localCertificate; } else if (File.Exists(Path.Combine(CertificateStore ?? ".", String.Format("{0}.crt", targethost)))) { localCertificate = X509Certificate.CreateFromCertFile( Path.Combine(CertificateStore ?? ".", String.Format("{0}.crt", targethost)) ); _certificateCache.Add(targethost, localCertificate); return localCertificate; } return _defaultCertificate; } private X509Certificate2 buildSelfSignedServerCertificate() { SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder(); sanBuilder.AddIpAddress(IPAddress.Loopback); sanBuilder.AddIpAddress(IPAddress.IPv6Loopback); sanBuilder.AddDnsName("localhost"); sanBuilder.AddDnsName(Environment.MachineName); X500DistinguishedName distinguishedName = new X500DistinguishedName($"CN=localhost"); using (RSA rsa = RSA.Create(4096)) { var request = new CertificateRequest(distinguishedName, rsa, HashAlgorithmName.SHA256,RSASignaturePadding.Pkcs1); request.CertificateExtensions.Add( new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature , false)); request.CertificateExtensions.Add( new X509EnhancedKeyUsageExtension( new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false)); request.CertificateExtensions.Add(sanBuilder.Build()); var certificate= request.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(3650))); //certificate.FriendlyName = "localhost"; using (FileStream fs = new FileStream("localhost.crt", FileMode.Create, FileAccess.Write)) fs.Write(certificate.Export(X509ContentType.Pfx)); return certificate; } } public void Dispose() { _socket?.Close(); _socket?.Dispose(); _socket = null; } } }