title: C# DNS Server redirection
body: <p>I am trying to make my own dns server from scratch which will filter out domains with adult content and redirect user to web page with info that url they are trying to load contains adult content and that is is blocked.</p>
  
  <p>So far so good, I have setup my own http server and I am replacing Ip addresses of blacklisted domain with my own server to display error (Not sure if this is right way to do it, and does other dna servers do it the same way).</p>
  
  <p>Problem occures when I try to blacklist domain with https setup in which case after redirection browser displays error about untrusted source or something similar.</p>
  
  <p>Is there a way to bypass this situation, or maybe to display my web page some other way? </p>
  
  <p><strong>SOLUTION:</strong></p>
  
  <p>It turned out it cannot be done with DNS as I planned, because I couldn't find out which domain is client tying to access so I could create fake certificate. After that I switched to proxy server instead of DNS server. </p>
  
  <p>When client tries to connect to proxy server (for my solution I used TcpListener) first request will be CONNECT and it wont be encrypted so I could read domain from "Host" header and decide if domain should be blocked. </p>
  
  <p>If blocked create fake certificate, open ssl connection (here is an example how to establish Ssl connection) and return block page as response, else with TcpClient make connection to real server and tunnel data both ways (Do not make SslStream, read and write directly to NetworkStream)</p>
  
  <pre><code>using(SslStream sslStream = new SslStream(context.ClientNetworkStream))
  {
      sslStream.AuthenticateAsServer(fakeCert);
  
      sslStream.Write(blockPageData, 0, blockPageData.Length);
  }
  </code></pre>
  
  <p>Also, how to create fake certificate:</p>
  
  <pre><code>public void CreateCertificate(string domain, string path, X509Certificate2 rootCert, string certPassword)
  {
      string dirPath = Path.GetDirectoryName(path);
  
      if (!Directory.Exists(dirPath))
      {
          Directory.CreateDirectory(dirPath);
      }
  
      SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder();
      sanBuilder.AddDnsName(domain);
  
      X500DistinguishedName distinguishedName = new X500DistinguishedName($"CN={domain}");
  
      using (RSA rsa = RSA.Create(2048))
      {
          CertificateRequest 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());
  
          //Generates a cert, but does not provide a private key.
          using (X509Certificate2 certificate = request.Create(rootCert, new DateTimeOffset(rootCert.NotBefore), new DateTimeOffset(rootCert.NotAfter), new byte[] { 0, 1, 2, 3 }))
          {
              X509Certificate2 certWithPrivateKey = certificate.CopyWithPrivateKey(rsa);
  
              byte[] certBuffer = certWithPrivateKey.Export(X509ContentType.Pkcs12, certPassword);
  
              using (Stream stream = File.Create(path))
              {
                  stream.Write(certBuffer, 0, certBuffer.Length);
              }
          }
      }
  }          
  </code></pre>