4

Im trying to create my own HTTPS proxy Server. For some reason, i got an exception when I try to read from sslstream object. Here is it:

An unhandled exception of type 'System.IO.IOException' occurred in System.dll

Additional information: Unable to read data from the transport connection: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.

Here is my code:

    public Server()
    {
        m_portnumber = 4434;
        m_tcplistener = new TcpListener(IPAddress.Any, m_portnumber);
        m_cert = createCertificate();
    }
    public void start()
    {
        m_tcplistener.Start();
        while (true)
        {
            TcpClient client = m_tcplistener.AcceptTcpClient();
            ClientHandler(client);
        }
    }

    private void ClientHandler(TcpClient client)
    {
        // A client has connected. Create the 
        // SslStream using the client's network stream.
        SslStream sslStream = new SslStream(
            client.GetStream(), false);
        // Authenticate the server but don't require the client to authenticate.
        try
        {
            sslStream.AuthenticateAsServer(m_cert,
                false, SslProtocols.Tls, true);
            // Display the properties and settings for the authenticated stream.
            DisplaySecurityLevel(sslStream);
            DisplaySecurityServices(sslStream);
            DisplayCertificateInformation(sslStream);
            DisplayStreamProperties(sslStream);

            // Set timeouts for the read and write to 5 seconds.
            sslStream.ReadTimeout = 5000;
            sslStream.WriteTimeout = 5000;
            // Read a message from the client.   
            Console.WriteLine("Waiting for client message...");
            string messageData = ReadMessage(sslStream);
            Console.WriteLine("Received: {0}", messageData);
        }
        catch (AuthenticationException e)
        {
            Console.WriteLine("Exception: {0}", e.Message);
            if (e.InnerException != null)
            {
                Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
            }
            Console.WriteLine("Authentication failed - closing the connection.");
            sslStream.Close();
            client.Close();
            return;
        }
        finally
        {
            // The client stream will be closed with the sslStream
            // because we specified this behavior when creating
            // the sslStream.
            sslStream.Close();
            client.Close();
        }
    }
    static string ReadMessage(SslStream sslStream)
    {
        // Read the  message sent by the server.
        // The end of the message is signaled using the
        // "<EOF>" marker.
        byte[] buffer = new byte[2048];
        StringBuilder messageData = new StringBuilder();
        int bytes = -1;
        do
        {
            bytes = sslStream.Read(buffer, 0, buffer.Length);

            // Use Decoder class to convert from bytes to UTF8
            // in case a character spans two buffers.
            Decoder decoder = Encoding.UTF8.GetDecoder();
            char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
            decoder.GetChars(buffer, 0, bytes, chars, 0);
            messageData.Append(chars);
            // Check for EOF.
            if (messageData.ToString().IndexOf("<EOF>") != -1)
            {
                break;
            }
        } while (bytes != 0);

        return messageData.ToString();
    }
    static void DisplaySecurityLevel(SslStream stream)
    {
        Console.WriteLine("Cipher: {0} strength {1}", stream.CipherAlgorithm, stream.CipherStrength);
        Console.WriteLine("Hash: {0} strength {1}", stream.HashAlgorithm, stream.HashStrength);
        Console.WriteLine("Key exchange: {0} strength {1}", stream.KeyExchangeAlgorithm, stream.KeyExchangeStrength);
        Console.WriteLine("Protocol: {0}", stream.SslProtocol);
    }
    static void DisplaySecurityServices(SslStream stream)
    {
        Console.WriteLine("Is authenticated: {0} as server? {1}", stream.IsAuthenticated, stream.IsServer);
        Console.WriteLine("IsSigned: {0}", stream.IsSigned);
        Console.WriteLine("Is Encrypted: {0}", stream.IsEncrypted);
    }
    static void DisplayStreamProperties(SslStream stream)
    {
        Console.WriteLine("Can read: {0}, write {1}", stream.CanRead, stream.CanWrite);
        Console.WriteLine("Can timeout: {0}", stream.CanTimeout);
    }
    static void DisplayCertificateInformation(SslStream stream)
    {
        Console.WriteLine("Certificate revocation list checked: {0}", stream.CheckCertRevocationStatus);

        X509Certificate localCertificate = stream.LocalCertificate;
        if (stream.LocalCertificate != null)
        {
            Console.WriteLine("Local cert was issued to {0} and is valid from {1} until {2}.",
                localCertificate.Subject,
                localCertificate.GetEffectiveDateString(),
                localCertificate.GetExpirationDateString());
        }
        else
        {
            Console.WriteLine("Local certificate is null.");
        }
        // Display the properties of the client's certificate.
        X509Certificate remoteCertificate = stream.RemoteCertificate;
        if (stream.RemoteCertificate != null)
        {
            Console.WriteLine("Remote cert was issued to {0} and is valid from {1} until {2}.",
                remoteCertificate.Subject,
                remoteCertificate.GetEffectiveDateString(),
                remoteCertificate.GetExpirationDateString());
        }
        else
        {
            Console.WriteLine("Remote certificate is null.");
        }
    }
    private static void DisplayUsage()
    {
        Console.WriteLine("To start the server specify:");
        Console.WriteLine("serverSync certificateFile.cer");
        Environment.Exit(1);
    }

    private X509Certificate createCertificate()
    {
        byte[] c = Certificate.CreateSelfSignCertificatePfx(
                   "CN=localhost", //host name
                    DateTime.Parse("2015-01-01"), //not valid before
                    DateTime.Parse("2020-01-01"), //not valid after
                    "sslpass"); //password to encrypt key file
        using (BinaryWriter binWriter = new BinaryWriter(File.Open(@"cert.pfx", FileMode.Create)))
        {
            binWriter.Write(c);
        }
        return new X509Certificate2(@"cert.pfx", "sslpass");
    }
}
Community
  • 1
  • 1
michal_h
  • 51
  • 7
  • 1
    The problem occurs on that line: bytes = sslStream.Read(buffer, 0, buffer.Length); – michal_h Jun 21 '16 at 18:42
  • Of course it does. You set a read timeout and MSDN says "If the read operation does not complete within the time specified by this property, the read operation throws an IOException." What did you expect to happen? – Dark Falcon Jun 21 '16 at 18:44
  • (If you were expecting to detect EOF with `messageData.ToString().IndexOf("")`, that's only going to be hit if the client literally sends you the bytes ``. Is your client doing that? Since I suspect your client is a web browser, I doubt it is.) – Dark Falcon Jun 21 '16 at 18:46
  • actually the client send me the url, i need to make the connection with the server (through me). so what exactly should i do? – michal_h Jun 21 '16 at 18:49
  • Im sure that it never stands on the line of EOF. it always through exception before – michal_h Jun 21 '16 at 18:55
  • Properly read and parse an HTTP request. In particular, an HTTP request consists of a series of lines terminated by `\r\n`. The end of the request header is indicated by a blank line (`\r\n\r\n`). You need to properly parse this to know when the request is complete and you need to proceed with your processing. Don't forget that some requests have a body also, which follows the header. – Dark Falcon Jun 21 '16 at 18:56
  • Wow - Thats Work - Thank you very much buddy! – michal_h Jun 21 '16 at 19:06

2 Answers2

3

This is not an SSL error, but a TCP one. You are connecting to an ip/port pair that is not listening. Is an an active reject, so it is not like you're reaching the IP and is telling you there is no port. Is a timeout, that could mean invalid IP or the target's firewall is ignoring you (intentionally).

My first suspicion would be line m_portnumber = 4434;. This is an unusual port number. You're sure is not a typo, and you wanted the HTTPS usual port (443) ? If you truly mean 4434, you need to check the network connectivity. Make sure the IP resolves properly, is reachable, the target is listening and the firewall is letting you in.

Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
0

I guess above code is taken from https://learn.microsoft.com/en-us/dotnet/api/system.net.security.sslstream?view=net-5.0

Was facing similar issue with ReadMessage(SslStream sslStream) on one of windows machine (Win10 1809).

There are two things to note here :

  1. sslStream.Read will read everything in authenticated network stream including Header and Body. So it's your responsibility to parse both and come out loop.

  2. In my case machine was taking really long time (more than 5 seconds) to read from the stream. So i had to remove time read timeout. Here it is

    sslStream.ReadTimeout = 5000; \\ By default it is infinite

Tarun Kumar
  • 729
  • 1
  • 8
  • 16